Modifying nonexistant DOM elements in WKWebView - javascript

I'm displaying a webpage in a WKWebView. To hide elements like the header or sidebars, I'm applying JavaScript. The problem is I'm using one script for various pages of the same site, and page elements different for different types of pages. If I do something like this:
let scriptURL = NSBundle.mainBundle().pathForResource("myscript", ofType: "js")
let scriptContent = String(contentsOfFile:scriptURL!, encoding:NSUTF8StringEncoding, error: nil)
let script = WKUserScript(source: scriptContent!, injectionTime: .AtDocumentEnd, forMainFrameOnly: true)
config.userContentController.addUserScript(script)
…
document.getElementById("header").style.display = "none";
for a nonexistent element, it errors out and the rest of the JavaScript doesn't get applied.

You'll have to check to see whether or not the element is valid before proceeding. Instead of putting if statements everywhere, you can just define a function as so:
var setElementDisplayStyle = function(id, style) {
var element = document.getElementById(id);
if(element) element.style.display = style;
}
Usage:
setElementDisplayStyle("header", "none");

Did you want to something like this
var header = document.getElementById("header")
if (header) {
header.style.display = "none";
}

Related

Using CSS media queries inside a JavaScript file

I am working on an already existing webpage and want to resize some elements using CSS media queries like width but i only have access to the script file, is there a way to do this without injecting CSS in my js file?
As of now i've tried injecting my css file line by line in my js file
Although less than ideal, it seems that this is possible by creating a MediaQueryList object from window.matchMedia(), and set inline styles by listening to events for changes on it.
Detailed guide on: MDN
Here is a quick example targeting #media (max-width: 640px):
(Open full page and try change window size to see effects)
const body = document.querySelector("body");
const title = document.querySelector("h1");
const media = window.matchMedia("(max-width: 640px)");
body.style.background = media.matches ? "pink" : "lightgreen";
title.innerText = "Open full page and try change window size";
media.addEventListener("change", (event) => {
if (event.matches) {
body.style.background = "pink";
title.innerText = `Matching: ${media.media}`;
}
if (!event.matches) {
body.style.background = "lightgreen";
title.innerText = `Not matching: ${media.media}`;
}
});
<h1></h1>
The best way would be to load a stylesheet that is hosted someplace you control, and then ask JavaScript to load it in the page that you want.
Code would look like:
function loadStylesheet( absPath ) {
const linkElement = document.createElement('link');
linkElement.rel = 'stylesheet';
linkElement.href = absPath;
linkElement.type = "text/css";
const head = document.head || document.querySelector('head');
head.insertAdjacentElement('beforeend', linkElement);
}
Then you would call loadStylesheet() with your stylesheet's URL as the parameter.

Is Facebook blocking my script via some kind of "defense mechanism" (maybe React based)?

I tried to run the following script with Tampermonkey to hide all images in Facebook for a more minimal usage experience, yet no image is hidden in any webpage there.
document.querySelectorAll("img").forEach(function(el) {
el.style.display = "none";
});
There are no errors in Tampermonkey.
The script is on at each relevant webpage under facebook.com.
Maybe Facebook's React has a way of filtering such a script? If so, I ask what can be done from my end, if at all.
Update:
Sadly all 3 answers didn't work. Try them (originals) in a script targeting all Facebook webpages if you don't believe me.
Try this
var images = document.getElementsByTagName('img');
for (var n = images.length; n--> 0;) {
var img = images[n];
img.setAttribute("src", "");
}
Your code is correct, but the problem is that the images are constantly being generated so you need to continually set the display.
setInterval(() => {
document.querySelectorAll("img").forEach(el => {
el.style.display = "none"
}
}), 30);
A better way would probably be to inject a global style 1.
function addGlobalStyle(css) {
var head, style;
head = document.getElementsByTagName('head')[0];
if (!head) { return; }
style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css;
head.appendChild(style);
}

Javascript hiding and showing dynamic content of a div

Currently I hide and show the content of a div like this:
var header = null;
var content = null;
var mainHolder = null;
var expandCollapseBtn = null;
var heightValue = 0;
header = document.getElementById("header");
content = document.getElementById("content");
mainHolder = document.getElementById("mainHolder");
expandCollapseBtn = header.getElementsByTagName('img')[0];
heightValue = mainHolder.offsetHeight;
header.addEventListener('click', handleClick, false);
mainHolder.addEventListener('webkitTransitionEnd',transitionEndHandler,false);
function handleClick() {
if(expandCollapseBtn.src.search('collapse') !=-1)
{
mainHolder.style.height = "26px";
content.style.display = "none";
}
else
{
mainHolder.style.height = heightValue + "px";
}
}
function transitionEndHandler() {
if(expandCollapseBtn.src.search('collapse') !=-1)
{
expandCollapseBtn.src = "expand1.png";
}
else{
expandCollapseBtn.src = "collapse1.png";
content.style.display = "block";
}
}
This is fine if the content is static, but I'm trying to populate my div dynamically like so.
This is called from an iphone application and populates the div with a string.
var method;
function myFunc(str)
{
method = str;
alert(method);
document.getElementById('method').innerHTML = method;
}
I store the string globally in the variable method. The problem I am having is now when I try expand the div I have just collapsed there is nothing there. Is there some way that I could use the information stored in var to repopulate the div before expanding it again? I've tried inserting it like I do in the function but it doesn't work.
Does anyone have any ideas?
to replicate:
Here is the jsfiddle. jsfiddle.net/6a9B3 If you type in text between
here it will work fine. I'm not sure
how I can call myfunc with a string only once in this jsfiddle, but if
you can work out how to do that you will see it loads ok the first
time, but when you collapse the section and attempt to re open it, it
wont work.
If the only way to fix this is using jquery I dont mind going down that route.
is it working in other browsers?
can you jsfiddle.net for present functionality because it is hard to understand context of problem in such code-shoot...
there are tonns of suggestions :) but I have strong feeling that
document.getElementById('method')
returns wrong element or this element not placed inside mainHolder
update: after review sample in jsfiddle
feeling about wrong element was correct :) change 'method' to 'info'
document.getElementById('method') -> document.getElementById('info')
I think you want to use document.getElementById('content') instead of document.getElementById('method') in myFunc.
I really see nothing wrong with this code. However, a guess you could explore is altering the line
content.style.display = "none";
It might be the case that whatever is displaying your html ( a webview or the browser itself) might be wiping the content of the elemtns, as the display is set to none

Trouble with iFrame.contentDocument

Basically, im making a javascript to refresh a page and it will find the price and buy the item when it goes up for the price desired.
I got it to work without the iframe, but I need to to work in the iframe, which is the problem ive reached.
If you went to this page: [ http://m.roblox.com/items/100933289/privatesales ]
and ran this code:
alert(document.getElementsByClassName('currency-robux')[0].innerHTML);
You would get an alert for the lowest price. In the code, this doesnt work (Hence, my problem.)
Try running the code below on this page to get it to work [ http://www.roblox.com/Junk-Bot-item?id=100933289 ]
var filePath = document.URL;
var itemid = filePath.slice(((filePath.search("="))+1));
var mobileRoot = 'http://m.roblox.com/items/';
var mobileEnd = '/privatesales';
var mobileFilePath = mobileRoot+itemid+mobileEnd;
var iframe2 = '<iframe id="frame" width="100%" height="1" scrolling="yes"></iframe>';
document.write(iframe2);
var iframe = parent.document.getElementById("frame");
iframe.height = 300;
iframe.width = 500;
iframe.src = mobileFilePath;
var price;
var snipe = false;
var lp = Number(prompt("Snipe Price?"));
document.title = "Sniping";
function takeOutCommas(s){
var str = s;
while ((str.indexOf(",")) !== -1){
str = str.replace(",","");
}
return str;
}
function load() {
if (snipe == false) {
tgs = iframe.contentDocument.getElementsByClassName('currency-robux');
price = Number((takeOutCommas(tgs[0].innerHTML)));
alert(price);
}
}
iframe.onload = load;
You might try having both pages — the one from "m.roblox.com" and the one from "www.roblox.com" — add the following up at the top of the head:
<script>
document.domain = "roblox.com";
</script>
Code from the different domains won't be allowed to look at each others page contents, but if you set the domains to the same suffix then it should work.
If you can't get it to work by sharing the same document.domain="roblox.com" code then you can try posting messages to the iframe.
Put this inside the iframe page:
window.addEventListener('message',function(e) {
});
In the parent page execute this to pass a message (can be a string or object, anything really) to the iframe:
document.getElementById("frame").contentWindow.postMessage({ "json_example": true }, "*");
Put this in the parent to listen for the message:
window.addEventListener("message", messageReceived, false);
function messageReceived(e) {
}
From inside the iframe posting a message back out:
window.parent.postMessage('Hello Parent Page','*');

Duplicate iframe: Copy head and body from 1 iframe to another

Simple question which I can't seem to find an answer of:
I have two iframes on a page and I'd like to copy the content of the first one to the second.
But I can't do it by just copying the url of the first iframe to the second since the containing page is a dynamic one.
This code does do it, but a lot of the page-formatting seems to get lost. And I don't know if it's cross-browser either.
iframe2.contentWindow.document.write(iframe1.contentWindow.document.body.innerHTML);
Can this be done?
Native JavaScript Solution As Asked For:
First, to make things simple I created 2 object literals:
var iframe1 = {
doc : undefined,
head : undefined,
body : undefined
};
var iframe2 = {
doc : undefined,
head : undefined,
body : undefined
};
Next, I put everything under iframe1's window.onload handler to make sure it was loaded fully:
document.getElementById("iframe1").contentWindow.onload = function() {
Then I assigned all of the object literal properties:
iframe1.doc = document.getElementById("iframe1").contentWindow.document;
iframe1.head = iframe1.doc.getElementsByTagName("head")[0];
iframe1.body = iframe1.doc.getElementsByTagName("body")[0];
iframe2.doc = document.getElementById("iframe2").contentWindow.document;
iframe2.head = iframe2.doc.getElementsByTagName("head")[0];
iframe2.body = iframe2.doc.getElementsByTagName("body")[0];
Next, I needed to create a couple functions removeNodes() and appendNodes() so that I could re-factor some code that is used for both <head> and <body> routines.
function removeNodes(node) {
while (node.firstChild) {
console.log("removing: " + node.firstChild.nodeName);
node.removeChild(node.firstChild);
}
}
and:
function appendNodes(iframe1Node, iframe2Node) {
var child = iframe1Node.firstChild;
while (child) {
if (child.nodeType === Node.ELEMENT_NODE) {
console.log("appending: " + child.nodeName);
if (child.nodeName === "SCRIPT") {
// We need to create the script element the old-fashioned way
// and append it to the DOM for IE to recognize it.
var script = iframe2.doc.createElement("script");
script.type = child.type;
script.src = child.src;
iframe2Node.appendChild(script);
} else {
// Otherwise, we append it the regular way. Note that we are
// using importNode() here. This is the proper way to create
// a copy of a node from an external document that can be
// inserted into the current document. For more, visit MDN:
// https://developer.mozilla.org/en/DOM/document.importNode
iframe2Node.appendChild(iframe2.doc.importNode(child, true));
}
}
child = child.nextSibling;
}
With those functions created, now all we have to do is make our calls:
console.log("begin removing <head> nodes of iframe2");
removeNodes(iframe2.head);
console.log("begin removing <body> nodes of iframe2");
removeNodes(iframe2.body);
console.log("begin appending <head> nodes of iframe1 to iframe2");
appendNodes(iframe1.head, iframe2.head);
console.log("begin appending <body> nodes of iframe1 to iframe2");
appendNodes(iframe1.body, iframe2.body);
... and finally, we close off the window.onload function:
};

Categories