Swift 3 Detect when WKWebView finishes loading AJAX site - javascript

I am trying to inject some javascript into my web view when it is navigated to a product page URL. The website doesn't reload when navigating to different pages, so to my understanding that means it is using Ajax.
The problem is I need the page to be fully loaded because the purpose of the javascript I am using is to automatically select the size drop down.
I tried to use the navigation delegate but since it's not reloading between pages it only gets called when the web view is first loaded.
What I have done is setup and observer by
webView.addObserver(self, forKeyPath: "URL", options: .new, context: nil)
and check if the current URL is the URL I want to inject the javascript on by
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "URL" {
guard var currentURL = webView.url?.absoluteString else { return }
if currentURL.lowercased().range(of: "products") != nil {
webView.evaluateJavaScript("document.getElementById('size-options').selectedIndex = 1")
}
}
}
The problem with this is it gets called right when the URL changes but not when the page finishes loading. I have got it to successfully work by adding a delay but that isn't a good solution because if the page doesn't load in time it won't work. Is there any way to know when a page like this finishes loading all its elements? The website I am working with is http://www.supremenewyork.com/mobile/#categories mobile site.

Related

Inplace redirect in JS

I'm writing a Chrome extension to seamlessly inject URL params on a certain domain.
For instance, when you're visiting search.com/?q=query, it should redirect you to search.com/q=query&new_param=1.
The following background script works fine:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
var url = changeInfo.url;
if (
url &&
url.startsWith("https://search.com/?") &&
!url.includes('&new_param=')
) {
chrome.tabs.update(tabId, {url: newUrl});
}
});
But this redirect creates 2 entries in navigation history: with and without the new parameter.
It is bad because going back in history - to the page without the parameter - triggers the redirect again. And you're stuck unless you manually jump 2 pages in history back.
How can I keep the Back button UX the same as without the extension?
https://developer.chrome.com/extensions/history
Javascript also has window.history.replaceState, but I'm not sure if that works within the chrome extension sandbox.

Splash does not render java script

My question is closely related to this post: Splash do not render the whole page
I am not able to configure splash in a way that in renders the dynamic part of the following website:
https://www.wunderground.com/history/daily/ro/slava-cerchez%C4%83/LRTC/date/2018-8-03?cm_ven=localwx_history
The page looks like this
But it should look like this
Here is the splash-script:
function main(splash, args)
assert(splash:go(args.url))
assert(splash:wait(5))
splash:set_viewport_full()
splash.private_mode_enabled = false
splash.indexeddb_enabled = true
splash.html5_media_enabled = true
return {
html = splash:html(),
png = splash:png(),
har = splash:har(),
}
end
I already tried to increase waiting time and to disable private mode. I would be very glad if someone could give a hint how to configure splash correctly so that it renders the java script part.
I had the same issue with the historical weather data on that site. I was unable to figure out how to get the page to render using Splash. However, using a Seleuium driven browser appears to render the page correctly. This isn't as ideal because you'll actually have to render the page, but it works.
Selenium: https://selenium-python.readthedocs.io/installation.html

iron-router: How to load a route completely just as when hitting refresh after loading a route?

I have a sign-in method in my Meteor application that redirects users to different routes after login in to the system. Here is my method:
Meteor.loginWithPassword(emailVar, passwordVar, function (err) {
if (err !== undefined) {
// error handling code
} else {
if (!Roles.userIsInRole(Meteor.userId(), 'active')) {
return Router.go('account-deactivated');
}
if (Roles.userIsInRole(Meteor.userId(), 'pharmacist')) {
return Router.go('pharmacist-dashboard');
}
if (Roles.userIsInRole(Meteor.userId(), 'admin')) {
return Router.go('admin-dashboard');
}
}
});
While this method works as expected, it produces some issues with my theme (AdminLTE) due to JavaScript loading problems (ex: app.min.js etc.). For example the sliding effects doesn't work on redirected page. But when I reload the page from the browser it starts to work as expected.
I know this is a separate issue that needs to be addressed. But if there is a way to completely reload a link in Meteor using iron-router it would be helpful. Specially when a page is transfered to a completely different user environment where a new set of JavaScript and CSS are used.
I went through the user documentations of iron-router but the fixes do not provide a solution.
Try using window.location.href to redirect.
Using Router.go is merely loading the template for the route you are linking to, whereas using window.location.href is loading the url as if it was a link you just clicked (actual refresh).
You'll need to use the actual url though, not the 'route name'.
window.location.href = "http://yourapp.com/route/here";

How Pinterest extension store (temporarily) images from a web page and then access them in an iframe?

I've been trying to create a Pin It button (extension of Pinterest) like chrome extension. What i tried is firing a script when extension is clicked which can iterate through all available images on a webpage, store it in localStorage. Now i need to call an iframe (different domain ofcourse) and access these images. Since one can access localstorage only from same domain, i'm quite confused how Pinterest manages to store all images from a web page (temporarily and not on their server) and then use it in an iframe.
I also saw the code of PinIt Button extension but i can't understand a thing from it as it is too much obfuscated/encrypted or whatever.
I've read about chrome.storage api and i haven't been able to understand it quite well. I'm not even sure if this is the thing i need to do in this case. (This is the first time i'm developing a chrome extension). Can anybody please throw some light on this and guide me the best possible way to achieve this functionality?
P.S. I've completed this extension without using <iframe> however i need to do it with <iframe>.
EDIT 1: I can't write complete code here but here is the flow/structure/my attempts
I start with background.js
chrome.browserAction.onClicked.addListener(function (tab) { /*This fxn fires when extension is clicked*/
chrome.tabs.executeScript(tab.id, {
"file": "bookmarklet.js"/*Calls this file*/
})
});
In bookmarklet.js:
jQuery('body').append('<iframe class="vw_parent" id="image-grabber-container" src="" style="height: 100% !important; width: 100% !important; position: fixed !important; margin: 0% auto !important; background: rgba(17, 17, 17, 0.9) !important; left: 0 !important; right: 0 !important; z-index: 999999999 !important; top: 0% !important;"></iframe>');
jQuery('img').each(function() {
//do some checks to determine what images need to be stored
var allImgs = jQuery(this);
localStorage.setItem('myLocalImgs', allImgs);
})
vwgrid = "https://mydomain/FileToBeInjectedInIframe.php";
jQuery('#image-grabber-container').attr('src', vwgrid);
Now in FileToBeInjectedInIframe.php
var abcde = localStorage.getItem('myLocalImgs');
console.log(abcde);
//This gives NULL value
EDIT 2: As per comments and answer by DelightedD0D, i want to explain How this extension works/should work
1. User is on any webpage and then clicks the extension
2. All the images available on that webpage are displayed in an iFrame
3. User can select multiple images from this iFrame and then post them to my website
4. Reason for iFrame: If user clicks on POST IMAGE button available in iFrame and he is not logged into our website for posting the image, he should see a login popup in the same iFrame
5. If not iFrame, how would i check if the user is logged in my website as i won't be able to read session/cookie of a different domain on a different domain.
For same reasons i believe (i'm not sure though), pinterest also display images in an iFrame
TL;DR, link to an example extension at the bottom ;)
Ok, your question is a bit broad but I'll try to explain the approach I would take if I wanted to do this.
First, I'd drop the iFrame. I can't see any reason to use one here and I just dont like them personally.
I would:
have a content script that is injected into all pages (or specific one if needed, whatever)
the script would have a javascript class that adds a chrome.runtime.onMessage.addListener to the page
this class would listen for messages from the extension
messages would be sent to this class to trigger functions and send back a response
for example a message like {pageAction:"getImages"} would trigger the getImages function in the injected class
getImages would get all the images and send them back to the extension
One note here, I prefer to work with images encoded as base64 strings with stuff like this rather than image urls from a whole bunch of different domains and all the CORRs, link protection,....etc. For that reason, I would have the getImages make an ajax call to encodeImagesToBase64.php a server passing an array of image urls. encodeImagesToBase64.php would return an array of base64 images which are then sent to the extension.
Now that the extension has an array of base64 images, Id save them to chrome.storage.local in the extension's storage area.
Now you can show them in the popup, show them in a new tab for editing, or whatever
If you want to show them on the page in an overlay, just make a function to do that in the javascript listener class we made and send a message to it with the images to display
To get you started, here is a javascript listener class I use to do similar things in my extensions.
(note that this relies on John Resig's Simple JavaScript Inheritance which I highly recommend using when writing Classes )
// IMPORTANT NOTE you must reload your extension via chrome://extensions
// AND reload the browser tab in order for changes to this file to be seen!!
var PageActionListener = Class.extend({
/**
* This class is meant to be injected into a webpage
* once there, it listens for messages from an extension
* messages must pass like {pageAction:"someAction", ... }
* where someAction should map to a function in this class
* responses should take the form
* {success:true, pageAction:"someAction", data:"someData", ... }
*/
attachListener: function() {
/**
* Attaches a chrome message listener to the current page
* this allows the extension to call
* the functions in this class from the context of the page
* and receive responses from those functions via messaging
*/
var _this=this;
// Listen for messages from the popup
chrome.runtime.onMessage.addListener(function (msg, sender, extensionCallback) {
// First, validate the message links to a function
if (msg.pageAction) {
// then make sure its a legit function in this class
if(typeof _this[msg.pageAction] === "function"){
// call that fucntion
_this[msg.pageAction](msg,function(response){
extensionCallback(response);
});
}
else extensionCallback({success:false,msg:msg,error:"Action not found"});
return true;
}
});
},
getImages:function(msg,callback){
/**
* Traverses the DOM looking for images and returning them
* as an array of base64 strings
* #param object msg The message that triggered this action
* #param function callback A function to be called when the action below is complete
* passes to callback the images gathered from the page
*/
var images = [];
var $images= $('img');
$images.each(function(){
var url = this.src;
images.push(url);
});
// convert images to base64
$.ajax({
type: "POST",
url: "https://somedomain.com/shared-resources/php/encodeImagesToBase64.php", // you'll need to update this to your path
data: {urls:images},
success: function(response) {
response.msg=msg;
send the response back to the extension
callback(response);
},
error: function(xhr, status, error) {
callback({success:false,msg:msg,error:error.Message})
}
});
},
// add other functions that need to be called by the extension here
});
And here is the contents of encodeImagesToBase64.php to convert the images to base64:
<?php
if (isset($_POST['urls']) ){
$imageStrings=[];
foreach($_POST['urls'] as $url){
$imageStrings[]=base64_encode(file_get_contents($url));
}
echo json_encode(['success'=>true,'images'=>$imageStrings]);
}else{
echo json_encode(['success'=>false,'error'=>'Error: No images to encode']);;
}
Here is an example extension that kindof does what you want.
It'll need a ways to go to meet your actual needs but, it should be enough for you to understand the concepts and the approach Ive proposed above.
NOTE the example extension uses a copy of encodeImagesToBase64.php that is hosted on my server, you'll need to host your own and update the ajax call with the path to it. Ill leave mine up for now so you can test it out, but dont count on it being around forever :)

How should Urls from User Options be matched in Content_Script? [duplicate]

This question already has answers here:
Optionally inject Content Script
(3 answers)
Closed 7 years ago.
I am writing an extension that has options which let users decide which sites they want the extension to run on.
Say the user has this site in the options
site action
stackoverflow.com/* change background css to blue
google.com/* change background css to green
I store these string in the options. When the content_script runs should I retrieve these strings from options, loop through each one, parse with a urlParser into parts, turn each part into a regex (escaping everything but *), and compare it with document.URL? I recently read that this kind of user options validation for Urls should be done through a background script too so I'm not sure which way to go or if there's a more obvious way to do it.
I think extensions like Adblocker and Vimium seem to have this functionality but for deciding which sites not to run on. I want to figure out how to decide which sites to run on.
Update to the Question: Since my content_script needs to run at document_start (before the page is loaded since it deals with editting the page appearance) as a content_script, will the background page be able to execute the content_script before the webpage is loaded at all?
Validated a web page's url should be "validated" via a background page because the user's options will be hosted in local storage in the context of the background page. Here is what I would do... (although it's more of a suggestion that an answer).
I am not sure how the actions on the right column of your list factor into your question, sorry.
(Also note, you would need to incorporate a library (external or self-written) that can parse globs into regex.)
manifest.json
permissions: ["tabs", "storage", "webRequest", "<all_urls>"]
background.js
//allow the webrequest to run on all urls
var filter = { urls: "<all_urls>" };
//receives url information from webrequest listener
function listen(details) {
getUserOpts()
.then(function(arrayOfWhitelistUrls) {
//you can't use globs here, need to use more powerful filtering mechanisms
if (arrayOfWhitelistUrls.indexOf(details.url) > -1) {
message();
}
});
}
//returns a promise containing user defined whitelist urls from background local storage
function getUserOpts() {
return new Promise(function(res, rej) {
chrome.storage.get("whitelist", function(data) {
//you are saving all localhost data as a string, so you need to parse it first
res(JSON.parse(data));
});
});
}
//messages content script and allows execution
function message() {
chrome.tabs.query({active: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {permission: true});
});
}
chrome.webRequest.onBeforeRequest.addListener(listen, filter)
contentscript.js
function listen(message) {
if (message.permission) {
if (message.permission === true) {
init();
}
}
}
//listen for message
chrome.runtime.onMessage.addEventListener(listen);
So the order in which things run is like:
background page listens to each web request
on each web request, the background page asynchronously fetches user options from local storage
if the url of the current tab passes your filter, message your content script
the content script receives the message and then runs
There might be an easier way to do this; the downside to this method is that you need to include the permission gateway in every content script you have.

Categories