I spent days trying to share one of my Firefox for Android extension objects with the webpages I also open from my extension (declared as resources). The thing is I have read a lot about the last year's changes about unsafewindow, so I tryed a very small example with the new functions but didn't work. I copied the examples and I also tryed my owns, but there is no way to copy existing objects with functionality. See, I have a VERY big object to clone in the content window, but I decided to test with a small one:
//From addon
var dog = {
name: 'Spike',
woof: function(){alert('woof woof!')}
};
And after that I tryed to copy this object into the active window:
//From addon
var contentWindow = window.BrowserApp.selectedBrowser.contentWindow;
contentWindow.dog = Components.utils.cloneInto(
dog,
contentWindow,
{cloneFunctions: true}
);
And after that, I tryed to check what was really copied:
alert(contentWindow.dog); //Shows: [object Object]
alert(contentWindow.dog.name); //Shows: Spike
alert(contentWindow.dog.woof); //Shows: undefined
So, I can clone the objects but no the functions, even when I declared "cloneFunctions: true".
I also tryed to create an empty object and then assign the functions (a lot of work thinking in my so big original object), and didn't work:
function greetme(user) {
return "cheers " + user;
}
var foo = Components.utils.createObjectIn(contentWindow,{defineAs: "foo"});
Components.utils.exportFunction(greetme, foo, {defineAs: "greetme"});
//foo is not an object in current window
So... Any idea is welcome, I really don't know what to do because theory and given examples doesn't work anymore.
Thanks (A LOT) in advance!!
https://blog.mozilla.org/addons/2014/04/10/changes-to-unsafewindow-for-the-add-on-sdk/
Your code is more or less correct already, however, you're running into trouble with XRay wrappers. And, in order for the content window (website) to actually see you dog, you need to waive the XRay wrapper on the content window as well.
I tested the following with the current Firefox for Android Nightly (sorry, my release Firefox is not configured for remote debugging).
Ran this in the Main Process (using the WebIDE):
var dog = {
name: 'Spike',
woof: function () {
alert(contentWindow.document.title + "\n" + this.name + ': woof woof!');
}
};
var contentWindow = BrowserApp.selectedBrowser.contentWindow;
// Need to unwrap this, so that we can actually set properties on the
// object itself and not just the wrapper. Aka. make "dog" visible to
// the actual script.
var unsafeWindow = Components.utils.waiveXrays(contentWindow);
// Define Window.dog (on the unsafe window, so that the website code
// can actually see it).
unsafeWindow.dog = Components.utils.cloneInto(dog, contentWindow, {
cloneFunctions: true
});
Then I switched over to the actual tab and tested:
dog.woof();
And it worked.
Related
I'm doing some studies in Javascript using Twitter website as example. In any website i can open Google Devtools and use the console to manipulate anything on the page, such as get DOM nodes, edit elements and so on.
For instance, when i open 'www.google.com', go to Devtools and type the command above :
console.log('testing');
I get 'testing' string showing in the console.
However, when i open 'www.twitter.com' and do the same thing, NOTHING happens. Nothing is shown in console, just an 'undefined' string as shown below :
Why this behaviour happens only on Twitter website ?
EDIT : Tried the solution proposed on
"delete" - restore native function not working for changed prototype, how then?
But did not work :
In Javascript you can modify your global objects, so it's possible for you to do something like
Array.prototype.push = function(element) {
this[this.length] = 'blah'
}
Now every time you add a element to any array it will always add 'blah';
const myArray = [];
myArray.push(1);
myArray.push(2);
myArray.push(3);
console.log(myArray);
// output ['blah', 'blah', 'blah']
In the twitter website they did the same, although the code is minified you can see it here:
, Y = ["log", "warn", "debug", "info"]
, K = function() {}
, Q = ""
Line 1414 file https://abs.twimg.com/k/en/init.en.caa653749241467e7dbb.js
To make it work again, copy each line and run it on your console (credits for this solution to Rob W):
var frame = document.createElement('iframe');
document.body.appendChild(frame);
console = frame.contentWindow.console
console.log('it works')
If you type in just console.log (without any brackets), you can list the code for the log function on that website. Doing this on Twitter gives you
function (){}
Which is indeed an empty function showing that they've overwritten the default. By contrast, the same on google.com gives
function log() { [native code] }
Which is the default.
In my logging helper class, I have the following:
this.myInfo = console.info.bind(console);
When I call my myInfo function from elsewhere, the calling object and line number are correctly retained and logged in the Chrome devtools.
When I run myInfo though, I also want to run another local function in addition to the console.info. Hence, I figured I could just wrap the above and it would work. I've come up with the following:
var obj = this;
this.myInfo = (function() {
console.info.apply(this, arguments);
myOtherFunc.apply(obj, arguments);
}).bind(console);
The problem is that unlike my first example, I lose the calling context for console.info, and the wrong line number and file are logged in the devTools.
How can I wrap the first example and retain the proper context for the console.info?
You can use getter. In getter you call your other function and then return console.info.bind(console) to caller.
Object.defineProperty(this, "myInfo", { get: function () {
myOtherFunc();
return console.info.bind(console);
}});
In case of passing arguments. You can define following function:
this.myInfo = function()
{
myOtherFunc.apply(null, arguments);
return console.bind.apply(console, arguments);
}
// example of call
this.myInfo(1,2,3)();
I've new solution. You can implement your console.log wrapper in separate JS file or evaluate it with sourceURL then go to Chrome DevTools settings and add "console-wrapper.js" url to blackbox pattern or blackbox this script by link when first message is arrived to console.
When script become blackboxed then all messages will have correct location in source code.
It works in last Google Chrome Canary build and will be available in stable in around two months.
eval("\
function myAwesomeConsoleLogWrapper() {\
console.log.call(console, arguments);\
makeAnotherWork();\
}\
//# sourceURL=console-wrapper.js");
Alexey Kozyatinskiy's approach is cool. However, if not-pretty code like this.myInfo(1,2,3)() is a more serious problem than ugly console output, you could use the wrapper you posted in your question and print needed filename and line number manually having it extracted from new Error().stack. I'd personnaly use Alexey's method unless there was a team working on this project.
As of Firefox 36, Function.__exposedProps__ was made unavailable. Instead if one wanted to expose a chrome JS object to be used in content scripts, they have to use Components.utils.cloneInto with the target scope as browser.contentWindow.wrappedJSObject.
If one does not turn on the cloneFunctions flag, only those attributes are cloned that are not functions. Turning the flag does clone functions too, but not those functions that are defined via the Function.prototype path. For those functions one has to export them via Components.utils.exportTo with the target scope as your exposed object.
Coming to the issue I'm facing. (As I am unable to put it in words, I am adding a MWE).
Chrome end JS:
function Foo(){
this._nFunc = "something";
this._func = function(){/*do something*/};
}
Foo.prototype.Bar = function(){
this._func();
}
Foo.prototype.FooBar = function(){
this._nFunc = "somthing else";
}
var myFoo = new Foo();
var targetScope = browser.contentWindow.wrappedJSObject;
targetScope.myExposedObject = Components.utils.cloneInto(myFoo, targetScope, {cloneFunctions:true});
Components.utils.exportFunction(myFoo.Bar, targetScope.myExposedObject , {defineAs:"Bar"});
Components.utils.exportFunction(myFoo.FooBar, targetScope.myExposedObject , {defineAs:"FooBar"});
Content end JS:
window.myExposedObject.FooBar(); // works
window.myExposedObject._func(); // works
window.myExposedObject.Bar() // error this._func is undefined
Upon logging the this scope received by the function Bar(), we get _func:(void 0), while _nFunc is logged correctly.
Questions:
Is there something I'm missing, or is this a limitation in Firefox? If it is a limitation, please suggest possible ways to workaround the limitation.
Initially I thought that Bar() was somehow unable to access the scope of the calling object, and I tried to supply it the scope as parameters, i.e., Foo.prototype.Bar = function(scope){ scope._func();} and window.myExposedObject.Bar(window.myExposedObject);. Interestingly upon logging, the scope object also turned out to be (void 0). Why is that? I am sure that I am missing something here. What I expected was that the exposed object would map to the original object and upon sending the exposed object as parameters the chrome end JS would be able to get the original object.
While what you're trying to do might be possible with the right combination of cloneInto/exportFunction and waiving of xrays i would suggest you simply load the unprivileged part of your class hierarchy directly into the target context with the subscript loader and only hook the minimal amount of privileged functions into the prototype once it has been created.
This should reduce the attack surface and also avoid headaches with inheritance.
Additionally, these may prove useful:
https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
https://developer.mozilla.org/en-US/docs/Components.utils.makeObjectPropsNormal
I currently have a Web Application that runs off a global Javascript-based API, and it is initialized like this:
var Api = {
someVar: "test",
someFunction: function() {
return "foo";
}
}
This API is shared across many "Widgets" that live in the Web Application, and they should all run off this single Api instance so they can pass data to each other.
AJAX is currently used to load these Widgets, for example in widgets/mywidget.html, and it's placed in, say, <div id='widget_<random number>'>...</div>
Certain other parts of the code may choose to add more functionality to Api, and it's currently done like this:
Api.myExtension = {
myNewFunction: function() {
return "bar";
}
}
However, some issues arise from this kind of usage:
Problem One: What if one Widget (these may be provided by third-parties) decides to hide some code within, and does something similar to Api = {}, destroying the global Api var everything lives on, and breaking the whole Application? Is it possible to protect this Api variable from being overwritten from outside? Only "extending" is allowed (adding new things), but "removing/changing" is not allowed. i.e.:
Api.foo = { test: "bar" } // allowed
Api.someVar = "changing the existing someVar"; // not allowed
The following code is located "inside" Api, for example:
var Api = {
Debug: {
Messages = new Array,
Write: function() {
Api.Debug.Messages.push("test"); // allowed
}
}
}
Api.Debug.Messages.push("test 2"); // not allowed
Probable Solutions I've Thought Of:
Suppose we simply use frames to resolve this issue. The Apis provided are now separate from each other. However, there's additional overhead when loading Api again and again if I have many Widgets running, and they can no longer communicate with the "Host" of the widgets (the page where frames reside in), for example, I may want to tell the host to show a notification: Api.Notify.Show("Test"), but it cannot do so because this Api is completely independent from other instances, and it cannot communicate with the "Host"
Using something like a "getter" and "setter" function for the Api to be read and written. I'm unsure on how to implement this, so any help on directions on how to implement this is welcome!
A mixture of 1/2?
There's no good way to prevent having a "third party" widget overwrite the a global variable. Generally it is the responsibility of whoever is putting together the final application to ensure that whatever JavaScripts they are using aren't littering the global namespace and conflicting. The best thing you can do in that direction is give your "Api" a nice, unique name.
What I think can help you a lot is something like the "revealing pattern", which would be a way of doing the "getters and setters" you mentioned, plus more if you needed it.
A simple, useless example would be like the following:
var Api = (function () {
// private variable
var myArray = [];
return {
addItem: function (newItem) {
myArray.push(newItem);
},
printItems: function () {
console.log("lots if items");
}
};
})();
Api.addItem("Hello, world");
Api.extensionValue = 5;
I think you should make a clear delineation of what is shared, or "singleton" data, and keep those items private, as with myArray in my example.
Make it a constant:
const Api = "hi";
Api = 0;
alert(Api); //"hi"
Take a look at
Object.freeze
More info here
Here is a code example from Mozilla's page:
var obj = {
prop: function (){},
foo: "bar"
};
// New properties may be added, existing properties may be changed or removed
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;
var o = Object.freeze(obj);
assert(Object.isFrozen(obj) === true);
// Now any changes will fail
obj.foo = "quux"; // silently does nothing
obj.quaxxor = "the friendly duck"; // silently doesn't add the property
// ...and in strict mode such attempts will throw TypeErrors
function fail(){
"use strict";
obj.foo = "sparky"; // throws a TypeError
delete obj.quaxxor; // throws a TypeError
obj.sparky = "arf"; // throws a TypeError
}
fail();
// Attempted changes through Object.defineProperty will also throw
Object.defineProperty(obj, "ohai", { value: 17 }); // throws a TypeError
Object.defineProperty(obj, "foo", { value: "eit" }); // throws a TypeError
However browser support is still partial
EDIT: see Kernel James's answer, it's more relevant to your question (freeze will protect the object, but not protect reassigning it. however const will) same issue with limited browser support though.
The only way (at least that I can think of) to protect your global variable is to prevent the Widgets from having a direct access to it. This can be achieved by using frames functions, as you suggested. You should create an object that contains all the functions that the Widgets should be able to use, and pass such to each Widget. For example:
var Api = {
widgetApi = {
someFunction: function(){
// ...
}
},
addWidget:function(){
var temp = this.widgetApi.constructor();
for(var key in this.widgetApi)
temp[key] = clone(this.widgetApi[key]);
return temp;
}
// Include other variables that Widgets can't use
}
This way, the Widgets could execute functions and communicate with the host or global variable Api. To set variables, the Widget would be editing its private object, rather than the global one. For every frame (that represents a Widget), you must initialize or create a copy of the widgetApi object, and probably store it inside an array, in such a way that an instance of a Widget is stored in the main Api object.
For example, given <iframe id="widget"></iframe>
You would do the following:
var widget = document.getElementById("widget");
widget.contentWindow.Api = Api.addWidget();
widget.contentWindow.parent = null;
widget.contentWindow.top = null;
Additionally, in every frame you would need to set the parent and top variables to null so that the Widgets wouldn't be able to access the data of the main frame. I haven't tested this method in a while, so there might be ways to get around setting those variables to null.
Firefox 4 implemented a new security wrapper called XrayWrapper and the docs that I have been able to find say, for functions, that "the default is for functions to be created as safe by default"
var MyObj = {};
MyObj.Util = {
myFunc : function() {
content = '<table class="tupleList"><tr><td>192.168.1.1</td></tr></table>';
return content;
}
};
content = MyObj.Util.myFunc();
console.log(content);
When in Greasemonkey's world, the above logs to Firebug
ok/ok: [object XrayWrapper [object Window]]
My question is how to get at that string that I made in the function of the MyObj object. I've tried printing the wrappedJSObject property of the wrapped content, but that just returns
ok/ok: [object Window]
Any help would be appreciated. Also, are there MDN documentation links that explain this stuff better?
The problem was you trying to set window.content which is a window property that you cannot change, then you returned window.content. Properly scoping the content variable fixes your problem for this reason; also renaming the variable will fix the issue.
If you're interested in why the example code works differently in a web page, then it does in a GM script, that is because window for a user script is a wrapped object.