Firefox removes web component properties when appended inside iframe - javascript

I am trying to append a web component DOM node comp with a property myprop inside an iframe.
const frame = document.createElement('iframe')
frame.src = 'about:blank'
frame.addEventListener('load', _ => {
console.log(comp.myprop) // "abc"
frame.contentDocument.body.appendChild(comp)
console.log(comp.myprop) // undefined in Firefox, "abc" in Chrome, Safari
})
This works perfectly in Chrome and Safari. However, Firefox seems to delete myprop after comp is appended to the body of the iframe.

Chrome and Firefox don't have the same interpretation of the Custom Element definition scope.
With Chrome, when you move a custom element from the main document to an inner <iframe>, it remains a (defined) custom element with all its methods and properties.
With Firefox, when you move a custom element from the main document to an inner <iframe>, because of frame isoation, the custom element is undefined in the context of the Frame, the element is an unknown tag with no custom property or custom method.

You could try to store the value of comp.myprop in some different variable and re-assign it on comp after adding comp to the iframe. Something like this:
frame.addEventListener('load', _ => {
console.log(comp.myprop);
var propStorage = comp.myprop;
frame.contentDocument.body.appendChild(comp);
//You might need to add if(comp.myprop === undefined) here
comp.myprop = propStorage;
console.log(comp.myprop);
})

Firefox is broken, (in more than just this way, in case you wondered why their market share is a shadow of it's former self), but you can work around this particular problem by restoring the prototype in the adoptedCallback of the class definition.
adoptedCallback()
{
Object.setPrototypeOf(this,savedConstructor.prototype);
}
You can't use the generic this.constructor.prototype as it's been reset, so you have to use an explicit reference to the prototype.

Related

open the "closed" shadowRoot of element inserted by other extension in chrome

well google translate extension in chrome, has popup feature, it displays translation of selected word instantly, I wanted to access those translations displayed by popup, but this popup element is shadowRoot("closed"), so javascript cant access its content, I red an article about that subject and author says:
But really there is nothing stopping someone executing the following JavaScript before your component definition.
Element.prototype._attachShadow = Element.prototype.attachShadow; Element.prototype.attachShadow = function () { return this._attachShadow( { mode: "open" } ); };
Is it possible to change attachShadow method of other extension? if so where should it be executed by my extension? background_script or maybe somewhere. I think each extension has its own enviroment and I have no chane to edit their methods. I wish I'm wrong :)
No need to override it.
There's a special method in the content script.
Chrome 88+:
let shadowRoot = chrome.dom.openOrClosedShadowRoot(element);
Firefox 63:
let shadowRoot = element.openOrClosedShadowRoot();
Combined:
let shadowRoot = chrome.dom?.openOrClosedShadowRoot(element)
|| element.openOrClosedShadowRoot();

JavaScript limits to prototype-based inheritance?

This is not a question about how to extend JS natives and I am well aware of the "dangers" involved with such activity. I am only trying to get a deeper understanding of how JavaScript works. Why is it that if I write the following:
CSSStyleSheet.prototype.sayHi = function() {
console.log('CSSStyleSheet says hi!');
};
var test = new CSSStyleSheet;
test.sayHi(); // Console output: CSSStyleSheet says hi!
I get the expected output from the sayHi function. But if I then query a style element and produce a CSSStyleSheet object
from it via the sheet property, the sayHi function is not defined:
var styleElm = document.querySelector('style'),
sheet = styleElm.sheet;
console.log('sheet', sheet) // Console output: sheet CSSStyleSheet {ownerRule: null,...
sheet.sayHi(); // Console output: Uncaught TypeError: sheet.sayHi is not a function
What is the reason for this? What would I have to do to make the sayHi function available to a CSSStyleSheet object produced via the sheet property - is it even possible?
The test was run in Chrome.
EDIT:
The reason I am looking into this is because I am trying to weigh my options when it comes to simplifying existing code. I have made an API to manipulate internal styles of a document loaded in an iFrame. It works as intended, but I would like to simplify the code if possible. It builds on the CSSOM API, which allows access to individual CSS style rules via numerical indexes. Having numerical indexes as the only way to access CSS rules seems quite rudimentary since you would never request a particular index unless you knew what rule the index pointed to. That is, you would always need to have info about the selector text. But it is the only way that makes sense in a broad context given the cascading nature of CSS (where you can have the same selector text as many times as you like).
However, my API keeps things in order so that every selector text is unique. Therefore, it makes sense to index the rules so that the main way of accessing them is via their selector text and my API does just that. However, things can quickly get less elegant when more than one level of rules are in play, i.e. if you have a number of media query rules containing their own index of CSS rules.
So I am just wondering if I can do anything to simplify the code and I must admit that were it not for the in this thread illustrated problems with hosted objects, i might have considered extending the CSSStyleSheet object.
Are there any other approaches, I might consider?
As it turned out, the issue is not about hosted object prototype limitations: while there are truly some, your particular example should work fine. The real problem is attempting to access this augmented prototype within iframe, which has its own global object. While there's a link between iframe's window and its host window, it's not used in the name resolving mechanism (few exceptions aside).
So the real challenge is to access host properties from within iframe. Now there are two ways of doing this: the easy one and the usual one.
The easy one is based on assumption that host and iframe share the same domain. With CORS concerns out of the way, you can connect those through parent property, as...
When a window is loaded in an <iframe>, <object>, or <frame>, its
parent is the window with the element embedding the window.
For example:
// host.html
<script>
CSSStyleSheet.prototype.sayHi = (space) => {
console.log(`CSSStyleSheet says hi! from ${space}`);
};
</script>
<iframe sandbox="allow-same-origin allow-scripts" src="iframe.html" />
// iframe.html
<button>Say Hi!</button>
<script>
Object.setPrototypeOf(CSSStyleSheet.prototype, parent.CSSStyleSheet.prototype);
document.querySelector('button').onclick = () => {
new CSSStyleSheet().sayHi('inner space');
};
</script>
... and it should work. Here, Object.setPrototypeOf() is used to connect CSSStyleSheet.prototype of a parent (host) window to iframe's own CSSStyleSheet.prototype. Yes, garbage collector suddenly has got more work to do, but technically this should be considered a problem of browser writers, not yours.
Don't forget to test this on proper HTTP(S) Server locally, as file:/// based iframes are not really cors-friendly.
If your iframe is from another castle domain, things get way more interesting. In particular, any attempt to access parent directly is just blocked with that nasty Uncaught DOMException: Blocked a frame with origin "blah-blah" message, so no free cookies.
Technically, however, there's still a way to bridge that gap. What follows is some food for thought, showing that bridge in action:
console.clear(); // check the browser console; iframe's one won't be visible here
CSSStyleSheet.prototype.sayHi = (space) => {
console.log(`CSSStyleSheet says hi! from ${space}`);
};
document.querySelector('button').onclick = () => {
new CSSStyleSheet().sayHi('outer space');
};
const html = `<button>Say Inner Hi!</button><br />
<script>
parent.postMessage('PING', '*'); // HANDSHAKE
document.querySelector('button').onclick = () => {
new CSSStyleSheet().sayHi('inner space');
};
addEventListener('message', (event) => {
const { data } = event;
if (data === null) {
delete CSSStyleSheet.prototype.sayHi;
}
else {
CSSStyleSheet.prototype.sayHi = eval(data);
}
}, false);
<` + `/script>`;
const iframe = document.createElement('iframe');
const blob = new Blob([html], {type: 'text/html'});
iframe.src = window.URL.createObjectURL(blob);
document.body.appendChild(iframe);
let iframeWindow = null;
addEventListener('message', event => {
if (event.origin !== "null") return; // PoC
if (event.data === 'PING') {
iframeWindow = event.source;
console.log('PONG');
}
}, false);
document.querySelector('input').onchange = ({target}) => {
if (!iframeWindow) return;
iframeWindow.postMessage(target.checked
? CSSStyleSheet.prototype.sayHi.toString()
: null,
'*'); // augment the domain here
};
<button>Say Outer Hi!</button>
<label><b>INCEPTION MODE</b><input type="checkbox" /></label><br/>
The key part here is passing the stringified function from host to iframe through postMessage mechanism. It can (and should) be hardened:
using proper domain instead of '*' on postMessage and checking event.origin within eventListener is A MUST; never ever use postMessage in production without that!
eval can be replaced with new Function(...) with some additional parsing for that handler code; as that prototype function should live until the page does, GC shouldn't be a problem.
Still, using this bridge may not be particularly less complicated than the approach you employ right now.
Your code should work.
You can also call it using sheet.__proto__.sayHi()
Your code (as written in the question) will work, because you are modifying the prototype object that is linked to by all instances of CSSStyleSheet (no matter when they were created).
The reference to the prototype object (more precisely: the [[Prototype]]) is examined dynamically every time a property look-up is attempted on an object without an own property that matches the requested property name. An own property is a property directly situated on an object.
In your case you are using the dot property accessor syntax sheet.sayHi. Property sayHi is not found as an own property, and so the prototype chain is traversed. It is then found on the prototype object that you modified on line 1. You then invoke the method located on that property using (), and 'CSSStyleSheet says hi!' is printed out.
Try it!
CSSStyleSheet.prototype.sayHi = function() {
console.log('CSSStyleSheet says hi!');
};
const test = new CSSStyleSheet;
test.sayHi(); // Console output: CSSStyleSheet says hi!
const styleElm = document.querySelector('style'),
sheet = styleElm.sheet;
sheet.sayHi()

IE9 unable to get property 'remove' of undefined or null reference

I am working on a site, where I sometimes need to load a big banner into my header. The header has some default styles, which I need to remove if the specific page has a banner. These extra styles are in a class, which is then removed server side if there is a banner present. It works across all browsers, except in IE9.
document.onreadystatechange = function () {
// Initialize app when document is "ready"
if (document.readyState == "complete") {
var dom = {};
dom.$header = document.querySelector('.js-header');
dom.$banner = document.querySelector('.js-banner-image');
resizeBanner();
}
}
function resizeBanner(){
if(dom.$banner && dom.$banner !== null && dom.$banner !== undefined) {
dom.$header.classList.remove('has-no-banner');
}
}
The browser halts when it tries to remove the class, because it is "unable to get property 'remove' of undefined or null reference". However, the variable is defined and the element exists in the DOM.
If I go to a page that doesn't have a banner, the function doesn't fire (this is expected behaviour), so logically it's not the conditional that's messed up, it finds dom.$banner just fine, but just to test I've tried giving the element an ID, and declare that right before my method. That did not solve the problem.
The script file is referenced in the bottom of my document with defer async.
What am I doing wrong here?
The .classList property is not supported in IE9. Use a more traditional way of adding/removing classes as shown here: Adding and Deleting from objects in javascript

Is the `extends` property deprecated in X-Tag?

The use-case for the extends property seems very straight-forward (http://www.x-tags.org/docs#custom-tag-registration-extends), however testing with the following, tag definition:
(function () {
xtag.register('dk-foo', {
extends: 'b',
lifecycle: {
created: function () {
this.innerHTML = '*FOO*';
}
}
});
}());
and markup:
<dk-foo>Hello BAR</dk-foo>
there doesn't seem to be any effect (i.e. the text is not bold), and worse, it breaks on Chrome.
I've tested IE11, FF28, Safari 5.1.17, and Chrome 33/35. Every browser, except Chrome, runs code in lifecycle.created (i.e. changes the text to *FOO*). If I remove the extends property it runs on Chrome as well.
I haven't been able to find any more documentation on extends than the documentation above, nor any tags that uses it (although I certainly haven't looked at all of them...).
Am I perhaps just using the extends property incorrectly..?
Per this comment:
When you extend an element, you need to use the is="" syntax in your
markup: . The is="" attribute is part of the standard,
it's the only way to create custom elements from native elements.
I tried it and you actually need is= and the extends. I don't like the is= so I'm actually just creating an inner element, in your case, an inner b.

What causes the error "Can't execute code from a freed script"

I thought I'd found the solution a while ago (see my blog):
If you ever get the JavaScript (or should that be JScript) error "Can't execute code from a freed script" - try moving any meta tags in the head so that they're before your script tags.
...but based on one of the most recent blog comments, the fix I suggested may not work for everyone. I thought this would be a good one to open up to the StackOverflow community....
What causes the error "Can't execute code from a freed script" and what are the solutions/workarounds?
You get this error when you call a function that was created in a window or frame that no longer exists.
If you don't know in advance if the window still exists, you can do a try/catch to detect it:
try
{
f();
}
catch(e)
{
if (e.number == -2146823277)
// f is no longer available
...
}
The error is caused when the 'parent' window of script is disposed (ie: closed) but a reference to the script which is still held (such as in another window) is invoked. Even though the 'object' is still alive, the context in which it wants to execute is not.
It's somewhat dirty, but it works for my Windows Sidebar Gadget:
Here is the general idea:
The 'main' window sets up a function which will eval'uate some code, yup, it's that ugly.
Then a 'child' can call this "builder function" (which is /bound to the scope of the main window/) and get back a function which is also bound to the 'main' window. An obvious disadvantage is, of course, that the function being 'rebound' can't closure over the scope it is seemingly defined in... anyway, enough of the gibbering:
This is partially pseudo-code, but I use a variant of it on a Windows Sidebar Gadget (I keep saying this because Sidebar Gadgets run in "unrestricted zone 0", which may -- or may not -- change the scenario greatly.)
// This has to be setup from the main window, not a child/etc!
mainWindow.functionBuilder = function (func, args) {
// trim the name, if any
var funcStr = ("" + func).replace(/^function\s+[^\s(]+\s*\(/, "function (")
try {
var rebuilt
eval("rebuilt = (" + funcStr + ")")
return rebuilt(args)
} catch (e) {
alert("oops! " + e.message)
}
}
// then in the child, as an example
// as stated above, even though function (args) looks like it's
// a closure in the child scope, IT IS NOT. There you go :)
var x = {blerg: 2}
functionInMainWindowContenxt = mainWindow.functionBuilder(function (args) {
// in here args is in the bound scope -- have at the child objects! :-/
function fn (blah) {
return blah * args.blerg
}
return fn
}, x)
x.blerg = 7
functionInMainWindowContext(6) // -> 42 if I did my math right
As a variant, the main window should be able to pass the functionBuilder function to the child window -- as long as the functionBuilder function is defined in the main window context!
I feel like I used too many words. YMMV.
Here's a very specific case in which I've seen this behavior. It is reproducible for me in IE6 and IE7.
From within an iframe:
window.parent.mySpecialHandler = function() { ...work... }
Then, after reloading the iframe with new content, in the window containing the iframe:
window.mySpecialHandler();
This call fails with "Can't execute code from a freed script" because mySpecialHandler was defined in a context (the iframe's original DOM) that no longer exits. (Reloading the iframe destroyed this context.)
You can however safely set "serializeable" values (primitives, object graphs that don't reference functions directly) in the parent window. If you really need a separate window (in my case, an iframe) to specify some work to a remote window, you can pass the work as a String and "eval" it in the receiver. Be careful with this, it generally doesn't make for a clean or secure implementation.
If you are trying to access the JS object, the easiest way is to create a copy:
var objectCopy = JSON.parse(JSON.stringify(object));
Hope it'll help.
This error can occur in MSIE when a child window tries to communicate with a parent window which is no longer open.
(Not exactly the most helpful error message text in the world.)
Beginning in IE9 we began receiving this error when calling .getTime() on a Date object stored in an Array within another Object. The solution was to make sure it was a Date before calling Date methods:
Fail: rowTime = wl.rowData[a][12].getTime()
Pass: rowTime = new Date(wl.rowData[a][12]).getTime()
I ran into this problem when inside of a child frame I added a reference type to the top level window and attempted to access it after the child window reloaded
i.e.
// set the value on first load
window.top.timestamp = new Date();
// after frame reloads, try to access the value
if(window.top.timestamp) // <--- Raises exception
...
I was able to resolve the issue by using only primitive types
// set the value on first load
window.top.timestamp = Number(new Date());
This isn't really an answer, but more an example of where this precisely happens.
We have frame A and frame B (this wasn't my idea, but I have to live with it). Frame A never changes, Frame B changes constantly. We cannot apply code changes directly into frame A, so (per the vendor's instructions) we can only run JavaScript in frame B - the exact frame that keeps changing.
We have a piece of JavaScript that needs to run every 5 seconds, so the JavaScript in frame B create a new script tag and inserts into into the head section of frame B. The setInterval exists in this new scripts (the one injected), as well as the function to invoke. Even though the injected JavaScript is technically loaded by frame A (since it now contains the script tag), once frame B changes, the function is no longer accessible by the setInterval.
I got this error in IE9 within a page that eventually opens an iFrame. As long as the iFrame wasn't open, I could use localStorage. Once the iFrame was opened and closed, I wasn't able to use the localStorage anymore because of this error. To fix it, I had to add this code to in the Javascript that was inside the iFrame and also using the localStorage.
if (window.parent) {
localStorage = window.parent.localStorage;
}
got this error in DHTMLX while opening a dialogue & parent id or current window id not found
$(document).ready(function () {
if (parent.dxWindowMngr == undefined) return;
DhtmlxJS.GetCurrentWindow('wnManageConDlg').show();
});
Just make sure you are sending correct curr/parent window id while opening a dialogue
On update of iframe's src i am getting that error.
Got that error by accessing an event(click in my case) of an element in the main window like this (calling the main/outmost window directly):
top.$("#settings").on("click",function(){
$("#settings_modal").modal("show");
});
I just changed it like this and it works fine (calling the parent of the parent of the iframe window):
$('#settings', window.parent.parent.document).on("click",function(){
$("#settings_modal").modal("show");
});
My iframe containing the modal is also inside another iframe.
The explanations are very relevant in the previous answers. Just trying to provide my scenario. Hope this can help others.
we were using:
<script> window.document.writeln(table) </script>
, and calling other functions in the script on onchange events but writeln completely overrides the HTML in IE where as it is having different behavior in chrome.
we changed it to:
<script> window.document.body.innerHTML = table;</script>
Thus retained the script which fixed the issue.

Categories