Safari extension unable to dispatchMessage to IFRAMEs in tab - safari-extension

I want to be able to dispatch a message from the global page to the content script running inside an IFRAME in one of the tabs. I found that the Safari extension API may have a design oversight that does not allow this. (As opposed to the Chrome extension API, which has more comprehensive support for messaging between various components of an extension).
Basically, I have an injected.js that is injected to all frames and their IFRAMEs. In injected.js, I have the following for catching messages from the global page:
safari.self.addEventListener("message", function(msg) {
/* do something according to the msg */
} , false);
In my global page, I have the following code that broadcasts messages to the injected scripts:
var broadcast = function(message_name) {
var ws = safari.application.browserWindows;
for (var i = 0; i < ws.length; i++) {
var ts = ws[i].tabs;
for (var j = 0; j < ts.length; j++) {
ts[j].page.dispatchMessage(message_name);
}
}
};
What I found is that the messages only reach the injected.js injected into the top frames of the tabs. The messages never reach those that are injected in the child IFRAMEs.
It appears that the SafariWebPageProxy class only dispatches messages to the top frame in the tab, in spite of the fact that the injected.js script is injected into the child IFRAMEs, and that they all register for the "message" event on safari.self.
Do you know how I can get the messages to the injected script in a child IFRAME, and that the IFRAME is from a different domain vs the top frame?
It appears that the oversight in the API is as a combination of the following flaws:
(1) communications are basically broadcaster-subscriber based. There is no point-to-point callback-based communication channel like Chrome's chrome.extension.sendRequest API;
(2) this would have been fine if the broadcast goes to all child IFRAMEs. However, that is not the case. SafariWebPageProxy.dispatchMessage() only send the message the to injected.js in the top frame.
Any suggestion?

I ran to the same issue. Background can send a message to an IFRAME only in response to an IFRAME message.
To get around the limitation, I've used polling: each IFRAME is sending messages regularly to the background page to give it an option to send a message.

I know this is really old, but I recently had to do the same thing and the situation with Safari has not improved.
Although not ideal, for safari you need to rely on window.postMessage().
The top level injected script acts as a relaying agent between any and all iFrames on the page, taking in messages and passing them on to the background as needed. As complicated as that it sounds, it's quite likely the only way of making communication between a cross-origin iframe and the background page possible.

Related

Multi-web server ajax callback with refresh on Enter

Question about how to send a jQuery callback with an onSuccess: refresh from a textInput when the user presses [Enter]. We use the [Enter] press to trigger a search callback.
Our GS Seaside app uses HAProxy. This means the onSuccess: script is handled by a different gem than the one that handles the callback. Because of this, users will sometimes get the refresh because the callback, which to them looks like a lost input (a browser F5 refresh shows the right state). If running single gem or in a VW / Pharo image this problem does not come up.
I can work around the problem by using...
async: false;
...but that prevents me from show any kind of waiting feedback (I normally use a busy gif).
So, the question is: in a multi-web server configuration, how can you code a callback to...
1 - show a busy gif
2 - trigger the search callback
3 - refresh the display when done
...all in that order.
Using a form submission callback is a problem because multiple text inputs can trigger the search, since the callback is 'set value + do search', by virtual of the default [Enter] press.
For the JS callback, I'm using...
self onKeyPress: (
(JSStream
on: '(window.event ? window.event.keyCode : event.which) == 13')
then: (canvas jQuery ajax callback: aBlock value: canvas jQuery this value))
It all works fine, except for the missing busy gif, due to the 'async: false'.
Any suggestions?
You can define a beforeSend and a complete handler to show and hide the loading indicator while the request is being processed. The global parameter set to false is meant to ignore your existing handlers to process request start and end (the mentioned spinner), and only use these defined in the particular instance of the JQAjax object.
((html jQuery ajax)
async: false;
global: false; "https://api.jquery.com/category/ajax/global-ajax-event-handlers/"
callback: aBlock value: canvas jQuery this value;
onBeforeSend: (html jQuery id: 'indicator') show;
onSuccess: ((html jQuery id: 'fieldId') load html: [:h | ]);
onComplete: (html jQuery id: 'indicator') hide;
That said, keep in mind that doing a synchronous AJAX call is discouraged since it will block the whole UI thread until the request is resolved.
So it's not completely clear how you manage the state in different worker images (gems, in this case) returning different things (probably because of having different sessions), so it's also not clear to me why doing an async XHR request will be served differently to doing it synchronously, I never experienced that.
From the small sample you mention, it can't be deduced what is the "refresh" part of your code. So maybe, providing more context will help us give you more accurate answers.
Fix ended up being trivial: needed to include 'event.preventDefault();' in the [Enter] key script. Seems obvious in hindsight.
if ((window.event ? window.event.keyCode : event.which) == 13) {
event.preventDefault();
};'
This problem is confirmed to be a narrow configuration symptom: GemStone with multiple gems. On every other configuration Seaside / javascript behaves as expected. I will follow this up as a vendor problem. Thanks to everyone that looked at it (this was also posted on other forums).

Scrape dynamic websites using dart in flutter app

I have a website that generates a list of items using some javascript and I am trying to scrape it inside my flutter app using beautiful soap package for dart. The thing is that I am unable to scrape the dynamic data generated by the java script. I want to implement a solution that allows me to grab the source code of the website after it fully loads inside the app. A hidden webview inside the app would be perfect but what is blocking me is that how to get the data after the webview loads. This is my main concern. Code examples would be appreciated. Also better practices are welcomed.
What I have came to realize is that scraping dynamic websites that contain some javascript or a website that you want to click in it using a certain script to scrape it properly is not possible over flutter mobile. What you should do is to move the scraping to the cloud by creating your own api then using this api to return the response to your app. This will make scraping easier since you will not have to update your app for every error you find in your scripts. Also imagine that the website that you target updates itself every week, then you will have to update your app every week and wait for approval from all the stores you are subscribed to. A simple example would be using cloud functions from firebase in combination with javascript by utilizing the puppeteer package. A simple video tutorial is here: Tutorial over youtube
After lots of research I did indeed find a way.
Basically loading a hidden webview and scraping the data off of it, then showing it on screen. Here's how..
Spinning a webview in the UI
The Visibility widget and width/height properties will make sure the webview is impossible to be seen by the user. I suggest showing a loading screen until the data is scraped.
Visibility(
visible: false,
maintainState: true,
child: Container(
height: 1,
width: 1,
child: WebViewPlus(
onWebViewCreated: (controller) async {
log.e("onWebViewCreated");
await model.onWebViewCreated(controller);
},
onPageFinished: (url) async {
log.e("onPageFinished");
await model.onPageFinished(url);
},
javascriptMode: JavascriptMode.unrestricted,
),
),
),
The actual scraping
onWebViewCreated(controller) async {
this.webViewController = controller;
// Load the URL
await controller.loadUrl("<Your Website URL Here>",headers:_apiService.getAuthHeader());
// Get the HTML of the webpage as a JSON object
String docu = await webViewController?.webViewController.evaluateJavascript('document.documentElement.innerHTML') as String;
// Convert from JSON to String
var jsonString = json.decode(docu);
// Parse the String to a HTML DOM to actually access the elements
var dom = parse(jsonString);
// Some logic I needed in my application by scraping
for (var child in dom.getElementById("autodl-log-tbody")!.children) {
feed.add(child.text);
}
}
Pro Tip : If you think the webpage might need a bit more time to load, you can stall the execution of the function by using await Future.delayed(Duration(Seconds:5)); before the line of code where you load the URL in the onWebViewCreated() function.

Get background variable value in content scripts

I have a var in background page (for example var x = 23). How I can get this variable in my content script? I tried this in content.js:
chrome.extension.getBackgroundPage().x;
But it doesn't work.
Send your data with messages, and listen on the receiving side.
Messages
Since content scripts run in the context of a web page and not the extension, they often need some way of communicating with the rest of the extension. For example, an RSS reader extension might use content scripts to detect the presence of an RSS feed on a page, then notify the background page in order to display a page action icon for that page.
You can not use chrome.extension.getBackgroundPage() in content scripts, it is not supported, as an alternative use epoch answer for message communication.
References:
Support for Content Scripts

YII getFlashes() not deleting?

Before jumping in with an answer, please make sure you understand my scenario.
I have ajax calls that CREATE flashes.
I have other ajax calls that FETCH the flashes as JSON.
What is currently happening: I click a button which creates the flash. After which I run a ajax call that executes:
public function actionGetAllFlashesAsJSON() {
$flashMessages = Yii::app()->user->getFlashes(true);
$returnResult = array();
foreach ($flashMessages as $key => $value) {
$newItem = array();
$newItem['message'] = $value;
$newItem['kind'] = $key;
$returnResult[]= $newItem;
}
print json_encode($returnResult);
die();
}
My problem is, when I execute this function twice in a row, it still keeps returning the flashes. However, if I refresh the site, it shows the error, and then if I press refresh again, it's gone. My theory is that page refresh is causing some other kind of deletion of messages... but what? And how can I force the deletion of these messages after I receive the message in the above code?
More background info: I am using the flashes as ERROR messages, but i want them to appear at the top of my site AS THEY ARE CREATED. Flashes might get created via Ajax, so I have javascript running to check for new messages, and display them, but my problem is it shows the messages several times, because they are not getting deleted after calling getFlashes?
The flash messages are controlled by SESSION variables, which Yii destroys when the page is loaded (probably somewhere quite deep in the framework). You will have to manually destroy all the previous flash messages at the start of the ajax request
You can use: getFlashes() to get all the existing flash messages
For the other flash message methods have a look at the CWebUser docs here

Communication Between WebView and WebPage - Titanium Studio

I am working in a Mobile project (using Titanium Studio), in which i have the below situation
1) My Mobile app contacts Rails backend to check some data, say check validity of a
user id.
2) I found a way to load web pages in Mobile app, i.e., WebView
3) I could able to load the desired url, ex http://www.mydomain.com/checkuser?uid=20121
which would return data like status:success
But i need to read this data to show whether the response from server is a success or failure, how do i achieve this?
NOTE : The above mentioned scenario is an usecase, but actually what happens is i load a third party url in WebView and when user enters the data and submits, the result will be posted back to my website url.
EDIT : So the process is like below
1) WebView loaded with third party url like http://www.anyapiprovider.com/processdata
2) User will enter set of data in this web page and submits the page
3) The submitted data will be processed by the apiprovider and it returns data to my web page say http://www.mydomain.com/recievedata
This is the reason why i am not directly using GET using HTTPClient
FYI : I tried to fire Ti.APP events right from the actual web page as suggested by few articles, but most of them says this will work only if the file loaded is in local and not a remote file. Reference Link
Please suggest me if my approach has to be improved.
Thanks
If you don't want to follow Josiah's advice, then take a look at the Titanium docs on how to add a webview.addEventListener('load',... event listener and use webview.evalJS() to inject your own code into the third party HTML.
Maybe you can inject code to trap the submit event and fire a Ti event to trigger the downloading of data from your website.
Communication Between WebViews and Titanium - Remote Web Content Section
I found a solution for my problem
1) Load the http://www.mydomain.com/checkuser?uid=20121 in a webview
2) Let user enter and submit data to third party url
3) Recieve the response from third party url and print only <div id="result">status:success</div> in http://www.mydomain.com/recievedata page.
4) Add event listener for the web view as follows
webView.addEventListener('load', function(data)
{
//Add condition to check if the loaded web page has any div with id = result (to check if this is /recievedata page)
alert(webView.evalJS("document.getElementById('result').innerHTML"));
});
The above alert would print the result status:success, read it in webview load event
and take actions in web accordingly.
It works fine for me.
Instead of loading it in a WebView why not just GET it using a HTTP Client? This is much cleaner, and more standards based:
var xhr_get = Ti.Network.createHTTPClient({
onload : function(e) {
// Here is your "status:success" string
var returnValue = this.responseText;
},
onerror : function(e) {
Ti.API.info(this.responseText);
Ti.API.info('CheckUserProgressOnActivity webservice failed with message : ' + e.error);
}
});
xhr_get.open('GET', 'http://www.mydomain.com/checkuser?uid=20121');
xhr_get.send();