문제

In Firefox's and Chrome's consoles, this works (alerts script content):

var script = document.createElement("script");
script.textContent = (
    function test() {
        var a = 1;
    }
);
document.getElementsByTagName("head")[0].appendChild(script);
alert(document.getElementsByTagName("head")[0].lastChild.textContent);

Using this code as a Greasemonkey script for Firefox works too.

Now, if want to add a "private method" do() to test() It is not working anymore, in neither Firefox/Chrome console nor in a Greasemonkey script:

var script = document.createElement("script");
script.textContent = (
    function test() {
        var a = 1;
        var do = function () {
            var b = 2;
        };
    }
);
document.getElementsByTagName("head")[0].appendChild(script);
alert(document.getElementsByTagName("head")[0].lastChild.textContent);

To make this work in a Greasemonkey script, I have to put all the code in a CDATA tag block:

var script = document.createElement("script");
script.textContent = (<![CDATA[
    function test() {
        var a = 1;
        var do = function() {
            var b = 2;
        };
    }
]]>);
document.getElementsByTagName("head")[0].appendChild(script);
alert(document.getElementsByTagName("head")[0].lastChild.textContent);

This is only works in a Greasemonkey script; it throws an error from the Firefox/Chrome console. I don't understand why I should use a CDATA tag, I have no XML rules to respect here because I'm not using XHTML.

To make it work in Firefox console (or Firebug), I need to do put CDATA into tags like <> and </>:

var script = document.createElement("script");
script.textContent = (<><![CDATA[
    function test() {
        var a = 1;
        var do = function() {
            var b = 2;
        };
    }
]]></>);
document.getElementsByTagName("head")[0].appendChild(script);
alert(document.getElementsByTagName("head")[0].lastChild.textContent);

This doesn't working from the Chrome console. I've tried adding .toString() at the end like many people are doing (]]></>).toString();), but it's useless.

I tried to replace <> and </> with a tag name <foo> </foo> but that didn't work either.

Why doesn't my first code snippet work if I define var do = function(){} inside another function?

Why should I use CDATA as a workaround even if I'm not using XHTML?

And why should I add <> </> for Firefox console if it's working without in a Greasemonkey script?

Finally, what is the solution for Chrome and other browsers?

EDIT:

My bad, I've never used do-while in JS and I've created this example in a simple text editor, so I didn't see "do" was a reserved keyword :p

But problem is still here, I've not initialized the Javascript class in my examples. With this new example, CDATA is needed for Greasemonkey, Firefox need CDATA between E4X <> </> and Chrome fails:

var script = document.createElement("script");
script.textContent = (
<><![CDATA[var aClass = new AClass();
function AClass() {
    var a = 1;
    var aPrivateMethod = function() {
        var b = 2;
        alert(b);
    };
    this.aPublicMethod = function() {
        var c = 3;
        alert(c);
    };
}
aClass.aPublicMethod();]]></>
);
document.getElementsByTagName("head")[0].appendChild(script);

Question: why?

도움이 되었습니까?

해결책 2

All rights to @BrockAdams Why is CDATA needed and not working everywhere the same way? for the idea to write everything in a function and insert content into the script tag, thanks.

Thanks to @polygenelubricants javascript get function body for getting the main() function content only and not the function with its content.

var script = document.createElement("script");
function main()
{
var aClass = new AClass();
function AClass()
{
    var a = 1;
    var aPrivateMethod = function() {
        var b = 2;
        alert(b);
    };
    this.aPublicMethod = function() {
        var c = 3;
        alert(c);
    };
}
aClass.aPublicMethod();
}
var entire = main.toString();
var body = entire.substring(entire.indexOf("{") + 2, entire.lastIndexOf("}"));
script.textContent = body;
document.getElementsByTagName("head")[0].appendChild(script);

"+2" after entire.indexOf("{") is needed for not selecting "{" character and not having a blank new line at the beginning of the script tag content.

The Javascript class function should not have the same name as the variable where we save the class instance, in Firefox aClass.aPublicMethod(); will only be called automatically the first time you execute the code (you can't override class function), Chrome don't care of the notation, it works.

So, I've choosed AClass for the class name and aClass for the class object.

Curiously, it seems Firefox is parsing/displaying the function the way he likes when we use .toString() method, for example when script is inserted, its content in Firefox Inspector will be shown in one line only. Firebug will format the code with a "tab" space before each new line even if we remove the spaces when inserting the main() function (due to the fact Firebug know there was the main() function before substring so it adds auto-space). Firebug adds a new line too before and after AClass() function; when function return value is saved in variable, like aPrivateMethod or aPublicMethod, it's displayed on 1 line. Another weird change with Firebug is that var aClass = new AClass(); becomes var aClass = new AClass;.

function AClass()
{
    // code
}

becomes

function AClass() {
    // code
}

and so on...

Chrome console always respect the spaces/new lines like you've written in the main() function.

I think it's the way debug console have been created and it has no importance. By the way, all this modifications I've talked about doesn't appear with Firebug when using E4X extension.

It's funny to see the difference when using function.toString() method instead of the E4X extension. Maybe someone has an explanation :)

Firefox console result with function.toString() method:

var aClass = new AClass; function AClass() { var a = 1; var aPrivateMethod = function () {var b = 2;alert(b);}; this.aPublicMethod = function () {var c = 34;alert(c);}; } aClass.aPublicMethod();

Firefox console result with E4X extension:

var aClass = new AClass(); function AClass() { var a = 1; var aPrivateMethod = function () { var b = 2; alert(b); }; this.aPublicMethod = function () { var c = 34; alert(c); }; } aClass.aPublicMethod();

Firebug result with function.toString() method:

    var aClass = new AClass;

    function AClass() {
        var a = 1;
        var aPrivateMethod = function () {var b = 2;alert(b);};
        this.aPublicMethod = function () {var c = 3;alert(c);};
    }

    aClass.aPublicMethod();

Firebug result with E4X extension and Chrome console result with function.toString() method (E4X not supported) are the same:

var aClass = new aClass();
function aClass() {
    var a = 1;
    var aPrivateMethod = function() {
        var b = 2;
        alert(b);
    };
    this.aPublicMethod = function() {
        var c = 3;
        alert(c);
    };
}
aClass.aPublicMethod();

Can be related, E4X is a clean way to do this, without substring hack but only supported by Firefox: Creating multiline strings in JavaScript

다른 팁

There are multiple errors/issues here:

  1. Adding private method do() did not work because do is a reserved word!
    This code works fine in both FF and Chrome (and in GM script):

    var functionVar = (
        function test() {
            var a = 1;
            var properlyNamedVariable = function() {
                var b = 2;
            };
        }
    );
    console.log (functionVar.toString() );
    

    If you use descriptive variable names, you will not only save yourself much grief in the future, you will almost never accidentally use a reserved word.

  2. Re: "To make this work in a Greasemonkey script, I have to put all code in a CDATA tag".

    Nope, that code did not work. What happened is that the error occurred in a eval'd scope in Greasemonkey's sandbox and was not reported to Firefox's error console. That is, it silently failed.
    If you'd tried to execute test(); from the console, you'd get an error: ReferenceError: test is not defined. Whereas, if you delete the var do = function ... code, and reload the page, then you could call test() from the console.

  3. Likewise, the script.textContent = (<><![CDATA[ ... code also did not work. It also silently failed and test() was not defined. This is a slightly different kind of silent failure than the previous example.

  4. Code that relies on the (<><![CDATA[ ... trick does not work in Chrome because Chrome does not support the inline processing of XML within javascript. This is a perk of Firefox which supports E4X, while Chrome doesn't.

  5. Please format your code, so that it's not such a chore to read.


So, in summary:

  1. Use descriptive names for variables, functions, etc.
  2. Beware of reserved words in the language you are using.
  3. Beware of silent failures. Unfortunately, these happen in a variety of Greasemonkey scenarios.
  4. Different browsers support different features.

Here's a technique that works in Firefox, Chrome, several other browsers, and Greasemonkey, Tampermonkey, Scriptish, etc. It also helps with debugging and testing because the code is separated from inline variable declarations:

  1. Define your function, or code normally:

    function testMyAdhocCode () {
        "use strict";    
        var importInteger   = 1;
        var imA_PrivateFunction = function () {
            var someOtherInteger = 2;
    
            console.log (
                "importInteger = ", importInteger,
                "  ||  someOtherInteger = ", someOtherInteger
            );
        };
    
        console.log ("Greetings from testMyAdhocCode()!");
        imA_PrivateFunction ();
    }
    


  2. Then inject it into the page like this (If you must. Best not to inject code at all, if you can help it.):

    addJS_Node (testMyAdhocCode);   //-- Function is just created.
    
    // OR
    addJS_Node (null, null, testMyAdhocCode); //-- Function is created and immediately run.
    
    // OR
    addJS_Node ("var someVar = 86;") //-- Adhoc code
    
    // OR
    addJS_Node ("preExistingFunction (42); ") //-- Run a preexisting function.
    


  3. Where addJS_Node() is included in your script as:

    function addJS_Node (text, s_URL, funcToRun, runOnLoad) {
        var D                                   = document;
        var scriptNode                          = D.createElement ('script');
        if (runOnLoad) {
            //--- Doesn't always fire on Chrome. Needs @run-at document-start.
            scriptNode.addEventListener ("load", runOnLoad, false);
        }
        scriptNode.type                         = "text/javascript";
        if (text)       scriptNode.textContent  = text;
        if (s_URL)      scriptNode.src          = s_URL;
        if (funcToRun)  scriptNode.textContent  = '(' + funcToRun.toString() + ')()';
    
        var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
        targ.appendChild (scriptNode);
    }
    




Update for the additional question:

The new problem is more of the same.

  • That's improper code, so it throws an error in the Firefox console.
  • It uses the E4X feature, so it will not work in Chrome.
  • It should throw the same error in Greasemonkey as it does in Firefox (They're nominally the same JS engine), but due to pure dumb luck about how GM sandboxes scripts, you get away with that erroneous code (for now).
  • Do not code that way! Use a function like addJS_Node().

If you really must inject code, the proper way to that is like this:

function main () {
    "use strict";   // Keep this line!
    var aClass = new aClass();

    function aClass() {
        var a = 1;
        var aPrivateMethod = function() {
            var b = 2;
            alert(b);
        };
        this.aPublicMethod = function() {
            var c = 3;
            alert(c);
        };
    }

    aClass.aPublicMethod();

    //-- PUT ALL OF THE REST OF YOUR INJECTED CODE HERE.
}

addJS_Node (null, null, main);

It is a bad idea to put everything in the global scope of the target page! But, if you insist upon doing it, the code is like this:

function aClass() {
    "use strict";   // Keep this line!
    var a = 1;
    var aPrivateMethod = function() {
        var b = 2;
        alert(b);
    };
    this.aPublicMethod = function() {
        var c = 3;
        alert(c);
    };
}

addJS_Node (aClass);
addJS_Node (
      'var aClass = new aClass();'
    + 'aClass.aPublicMethod();'
);
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top