How can I tame multiple event listeners in a Fennec extension? - mobile

I'm trying to write a restartless add-on for Firefox Mobile that will insert content onto specific web pages. It all seems to work OK until I try disabling then re-enabling the add-on, at which point I get multiple responses to the page load event, and I can't figure out a way to sort them out.
Since Fennec uses the Electrolysis multi-process platform, I know that I need to split my code into chrome and content scripts. My bootstrap.js looks something like this (trimmed for clarity):
function startup(data, reason) {
mm = Cc["#mozilla.org/globalmessagemanager;1"].getService(Ci.nsIChromeFrameMessageManager);
mm.loadFrameScript(getResourceURISpec('content.js'), true);
}
function shutdown(data, reason) {
let mm = Cc["#mozilla.org/globalmessagemanager;1"].getService(Ci.nsIChromeFrameMessageManager);
mm.sendAsyncMessage("GeoMapEnh:Disable", {reason: reason});
}
function install(data, reason) {}
function uninstall(data, reason) {}
Basically, the bootstrap.js just launches a content script, and sends a message to tell it to clean up on shutdown. The content.js sets up an eventListener to watch for page loads, that looks a bit like this:
addMessageListener("GeoMapEnh:Disable", disableScript);
addEventListener("DOMContentLoaded", loadHandler, false );
function loadHandler(e) {
LOG("Page loaded");
// Do something cool with the web page.
}
function disableScript(aMessage) {
if (aMessage.name != "GeoMapEnh:Disable") {
return;
}
LOG("Disabling content script: " + aMessage.json.reason);
try {
removeEventListener("DOMContentLoaded", loadHandler, false );
removeMessageListener("GeoMapEnh:Disable", disableScript);
} catch(e) {
LOG("Remove failed: " + e);
}
}
function LOG(msg) {
dump(msg + "\n");
var consoleService = Cc["#mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
consoleService.logStringMessage(msg);
}
When I first run the extension, everything works fine. An instance of content.js is executed for each browser tab (and any new tabs I open) and my eventListener detects the page loads it is supposed to via the DOMContentLoaded event. When I disable the extension, everything still seems OK: page loads stop being detected.
When I re-enable the extension, it all goes wrong. I still get an instance of content.js executing for each open tab, but now, if I open new tabs, DOMContentLoaded triggers mutiple eventListeners and I can't distinguish which one should handle the event. Worse yet, some of the eventListeners are active, but do not give debug info via my LOG function, and do not all of them get removed if I disable the extension a second time.
I do want to watch all browser tabs, including any new ones, but I only want my extension to insert content on the page that triggers it, and only once per page load. I've tried the following without success:
Calling e.stopPropagation() to stop the event being passed to other listeners. No effect.
Calling e.preventDefault() and testing e.defaultPrevented to see if the event has already been handled. It never has.
Testing if (this === content.document) to see if the eventListener has been triggered by its own page content. Doesn't work, as I get multiple "true" responses.
Making the eventListener capturing. No effect.
Using the load event rather than DOMContentLoaded.
I can't set a shared variable to say the event has been handled as under Electrolysis, the different eventListeners will be executing in different contexts. Also, I wouldn't be able to distinguish between multiple tabs loading the same page and one page load being detected multiple times. I could do this via IPC message passing back to the chrome bootstrap script, but I then I wouldn't know how to address the response back to the correct browser tab.
Any ideas? Is this a Firefox bug, or am I doing something silly in my code? I am using Fennec Desktop v4 for development, and will target Fennec Android v6 for production.

Related

Navigating back from other app breaks click handler

For our company we have a lot of devices rolled out with Mobicontrol Soti. This allows us to lock the device in something called a kiosk mode which disables the use of the homescreen and provides a custom screen that only have a set off apps we can decide.
One of the provided apps is a Ionic app that opens links in a browser (Soti Surf) but this gives 2 problems.
code
HTML:
<div (click)="$ctrl.doTheThing()"> something </div>
JS:
private doTheThing() {
this.inAppBrowser.create('surfs://' + url.replace(/^(https?:|)\/\//, ''), '_system');
}
First issue
First of all when I use the android back button the click doesn't seem to work anymore (I've put an alert in the first line of the doTheThing function, but nothings shows up).
Other buttons in the app seem to work just fine, when using the switch app button it also works
I tried:
preventDefault()
stopPropagation()
using the tappable attribute
(tap) instead of (click)
but none seem to work. Does anybody have an idea for fixing this?
Second issue
Note: this is less important
When opening a link it remembers the last page(in soti surf) so by using the back arrow it first navigates to the last link and when it has no more back locations it goes back to the app
I tried:
using the return value of the inAppBrowser.create() and calling close() when returning to my app
Version info
#ionic-native/core version: 4.16.0
cordova-android: 7.1.4
cordova -v: 8.1.2 (cordova-lib#8.1.1)
npm -v: 6.4.1
ionic: 4.12.0
nodeJS: 11.1.0
Here in ionic you can prevent device back button by using Navbar Class and using Lifecycle hook method
Here is a sample
ionViewDidLoad() {
this.navBar.backButtonClick = (e: UIEvent) => {
// todo something
if (condition )
{
this.getSaveDialog();
} else{
this.navCtrl.pop();
}
})
}
For opening In Appbrowser in your app, will lose control of your events, you can't track and manage the
state of pages.
It can be maintain by browser. you need to place hacks or alternative in your application which is opening in APP

AngularJS testing with Protractor - chaining promises- How to wait for animation to complete?

I am currently developing an automated test suite for an AngularJS application developed by my company. I have already designed & developed a number of test cases, which are running & passing/ failing as expected.
However, with the test that I am currently developing, I seem to have run into an issue when chaining promises, and am unable to solve it... The test script as it stands is:
it('should navigate to the browser page', function() {
console.log("Start browser page test");
browser.waitForAngularEnabled(false);
browser.wait(EC.visibilityOf(pagesMenuBtn), 10000).then(
browser.actions().mouseMove(pagesMenuBtn).perform().then(
//expect(pageBrowserBtn.isDisplayed()).toBeTruthy();
browser.wait(EC.visibilityOf(pageBrowserBtn), 12000).then(
pageBrowserBtn.click().then(
function() {
console.log("Browser menu button clicked");
//browser.pause();
}).then(
browser.wait(EC.visibilityOf(browserPageTagsLink), 20000).then(
function(){
console.log("End browser page test (then call)");
expect(browser.getCurrentUrl()).toBe(VM + '/#/pages/browser');
}
)
)
)
)
);
});
When I run my test script (I have a number of other tests that run before this one), the first few run & pass successfully, then when this test starts to execute, the console shows:
Started
....Start browser page test
Browser menu button clicked
F
Failures:
1) App should navigate to the browser page
Message:
Failed: Wait timed out after 20010ms
Stack:
TimeoutError: Wait timed out after 20010ms
at WebDriverError (C:\Users\path\selenium-webdriver\lib\error.js:27:5)
So from the output displayed in the console, it's clear that the test executes correctly as far as the console.log("Browser menu button clicked); statement, which indicates that a click has been performed on the menu button as I intend (i.e. the menu button clicked is displayed on a popup menu that is only shown when the cursor is hovering over the pagesMenuBtn element, so that indicates that the line
browser.wait(EC.visibilityOf(pageBrowserBtn), 12000).then(
is executed correctly).
Since the console.log("Browser menu button clicked"); statement is also displayed in the console, that indicates that the
pageBrowserBtn.click().then(
is also executed correctly.
But for some reason, the test is then timing out after the 20 seconds it waits for the browserPageTagsLink element to be displayed.
This browserPageTagsLink element is just a hyperlink element displayed on the 'Browser' page that my test is trying to navigate to- I am waiting for it to be visible so that I know that the page has loaded before I check that the URL is correct...
But as I watch what's happening in the browser window where these tests are run, and what's displayed in the console while my tests are running, the script seems to 'get stuck'/ pause/ 'hang' for a while after the Browser menu button clicked message is displayed in the console, and I can see from watching the browser window that this button was never actually clicked- the popup menu where this button is displayed is shown very briefly: the line browser.actions().mouseMove(pagesMenuBtn).perform() is causing the cursor to hover over the button required to show the sub-menu, but it seems that the click is performed before the sub-menu element has fully finished loading (there is some animation on the element- it appears to 'fade into view'), but I think that the click is happening before the element has fully finished loading, and so it's possibly not registering correctly?
How can I cause my test to wait for the animation to complete before trying to click the sub-menu menu item, so that the click registers with the element correctly?
I tried doing this with the line:
browser.wait(EC.visibilityOf(pageBrowserBtn), 12000).then(
It seems that the EC.visibilityOf(...) condition is met as soon as the element starts to become visible, rather than waiting until it is fully opaque, but that the
pageTagBrowserBtn.click().then(
line, which is called as soon as the condition is true can't actually be performed at this point- presumably because the element is not 'fully' visible at the point at which it's clicked?
How can I make my test wait for the animation (which has been implemented using CSS) to complete before it tries to click on the element?
I had a look on the Protractor API for anything about animations, but it only seems to provide the function allowAnimations()...
Edit
The animation for this element is set in the CSS with:
/* Animation for moving lists in configuration */
.list-fade.ng-enter,
.list-fade-leave.ng-leave {
-webkit-transition: all linear 0.5s;
transition: all linear 0.5s;
}
i.e. it should take 0.5 seconds for the element to be displayed when the cursor hovers over its parent element.
I tried adding a call to browser.wait(), so that my test would wait for the element to be fully displayed before trying to click on it- I updated the part of my test that is sending the click to:
browser.actions().mouseMove(pagesMenuBtn).perform().then(
//expect(pageTagBrowserBtn.isDisplayed()).toBeTruthy();
browser.wait(EC.visibilityOf(pageTagBrowserBtn), 12000).then(
browser.wait(10000).then(
pageTagBrowserBtn.click().then(
function() {
console.log("Tag Browser menu button clicked");
I told it to wait for 10 seconds at this point to ensure that it definitely gave the element enough time to load (according to the CSS, it should only take 0.5 seconds to be displayed), but for some reason, my test is still failing due to the same timeout issue.
Anyone have any suggestions?
Looking at your tests I'm wondering why you have to chain your promises like that. On a fully angular page Protractor is supposed to automatically chain promises, ie when I write a test like this
driver.get(“http://www.google.com”);
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
driver.findElement(webdriver.By.name('btnG')).click();
driver.getTitle().then(function(title) {
console.log(title);
});
it actually is executed in a synchronous manner like this:
driver.get(“http://www.google.com”).
then(function() {
return driver.findElement(webdriver.By.name('q'));
}).
then(function(q) {
return q.sendKeys('webdriver');
}).
then(function() {
return driver.findElement(webdriver.By.name('btnG'));
}).
then(function(btnG) {
return btnG.click();
}).
then(function() {
return driver.getTitle();
}).
then(function(title) {
console.log(title);
});
If your page is fully Angular most of what you've written shouldn't need the promises, and that my be causing timing issues with your test. Have you tried writing the test without chaining promises?
That said, you might also try EC.elementToBeClickable(yourElement) instead of waiting for EC.visibilityOf() if you haven't already.

How to Post Data before Pop-Up?

I have an application where the user can edit content, then preview that content. Before preview, I need to have the content saved ($http.post to the server). I'm using angular and because the save routine is run through a different controller the code to save looks like:
$scope.$broadcast('save');
var saveCompletedListener = $scope.$on('saveCompleted', function () {
// this call doesn't work (because it waits for data save):
window.open($scope.contentUrl);
});
// this call does work, but shows old data in the popup:
window.open($scope.contentUrl);
The problem is the window.open call is getting blocked in all browsers. If I just call the window.open without waiting for save, it opens fine (it just shows the content from the previous save). So, is there some way in angular to show the popup, but have it wait to load the url until save has completed?

Webshims - Show invalid form fields on initial load

(Follow on questions from Placeholder Hidden)
I'd like my form to validate existing data when it is loaded. I can't seem to get that to happen
I jQuery.each of my controls and call focus() and blur(), is there a better way than this? I tried to call ctrl.checkValidity(), but it wasn't always defined yet. When it was, it still didn't mark the controls.
I seem to have a timing issue too, while the focus and blur() fire, the UI does not update. It's as if the Webshims are not fully loaded yet, even though this fires in the $.webshims.ready event.
I also tried to call $('#form').submit(), but this doesn't fire the events as I expected. The only way I could make that happen was to include an input type='submit'. How can I pragmatically case a form validation like clicking a submit button would?
Here's a jsFiddle that demonstrates the problem. When the form loads, I want the invalid email to be marked as such. If you click the add button it will be marked then, but not when initially loaded. Why?
Focus and blur in the control will cause it to be marked.
BUT, clicking ADD will too (which runs the same method that ran when it was loaded). Why does it work the 2nd time, but not when initially loaded?
updateValidation : function () {
this.$el.find('[placeholder]').each(function (index, ctrl) {
var $ctrl = $(ctrl);
if( $ctrl.val() !== "" && (ctrl.checkValidity && !ctrl.checkValidity()) ) {
// alert('Do validity check!');
$ctrl.focus();
$ctrl.blur();
}
});
}
I see this in FF 17.0.5. The problem is worse in IE9, sometimes taking 2 or 3 clicks of ADD before the fields show in error. However, I get errors on some of the js files I've liked 'due to mime type mismatch'.
This has to do with the fact, that you are trying to reuse the .user-error class, which is a "shim" for the CSS4 :user-error and shouldn't be triggered from script. The user-error scripts are loaded after onload or as soon as a user seems to interact with an invalid from.
From my point of view, you shouldn't use user-error and instead create your own class. You can simply check for validity using the ':invalid' selector:
$(this)[ $(this).is(':invalid') ? 'addClass' : 'removeClass']('invalid-value');
Simply write a function with similar code and bind them to events like change, input and so on and call it on start.
In case you still want to use user-error, you could do the following, but I would not recommend:
$.webshims.polyfill('forms');
//force webshims to load form-validation module as soon as possible
$.webshims.loader.loadList(['form-validation']);
//wait until form-validation is loaded
$.webshims.ready('DOM form-validation', function(){
$('input:invalid')
.filter(function(){
return !!$(this).val();
})
.trigger('refreshvalidityui')
;
});

Mobile Firefox (Fennec) add-on: executing code on page loads

I am trying to write a mobile firefox plugin that executes a piece of javascript code automatically every time a page loads. I had written some code for an earlier version of Fennec, but with the multi-processing system in the newer Fennec version (https://wiki.mozilla.org/Mobile/Fennec/Extensions/Electrolysis/), this code had to be ported. I based myself on a tutorial from http://people.mozilla.com/~mfinkle/tutorials/ to get a version working that executes a piece of code whenever an option is selected in the browser menu. This solution consists of two parts, namely overlay.js (for the main (application) process) and content.js (for the child (content) process). Overlay.js is loaded in overlay.xul, while content.js is loaded for new tabs via the following code in overlay.js:
window.messageManager.loadFrameScript("chrome://coin/content/content.js", true);
The code in overlay.js sends a message to content.js whenever the option in the browser menu is clicked, and the required code is then correctly executed (some script tags are simply added to the head of the page). However, I don't know how to execute code automatically on a page load. I tried the following in content.js:
function addCoin(aMessage) { ... }
// this executes the desired code every time an option is clicked in the browser menu
addMessageListener("coin:addCoin", addCoin);
// this attempts to execute the code on every page load; i.e., after this script has
been loaded for the new tab
addCoin(null);
The last statement however has no effect. Then, I tried adding the following statement at the end:
sendAsyncMessage("coin:scriptLoaded", { });
This statement sends a message to the overlay.js script, which registers a listener for this message and in response simply sends the same message as when the option in the browser menu is clicked, i.e., "coin:addCoin". However, this didn't work either. Finally, I tried looking for certain events the overlay.js script could listen for (something like "tabOpened" or something), but couldn't find anything.
Does anyone have any ideas on how to automatically execute code on every page load?
Regards,
William
In your content.js script you can simply register an event listener for the "load" event, just like you would have in the old single process Firefox:
addEventListener("load", someFunc, true);
This will call "someFunc" any time a webpage loads in the tab.
Any global code in content.js is executed when the tab is initial created, not when a page loads. Use global code to set up event listeners or message listeners. The web content will still fire events you can catch in the content.js (child script).
This worked for me.
in content.js:
var addEventListener;
if (window.BrowserApp) { // We are running in Mobile Firefox
addEventListener = window.BrowserApp.deck.addEventListener;
} else {
var appcontent = document.getElementById("appcontent");
if (appcontent) {
addEventListener = appcontent.addEventListener;
}
}
if (addEventListener) {
var onDOMContentLoaded = function() { /* Code here */ };
addEventListener("DOMContentLoaded", onDOMContentLoaded, true);
var onLoad = function() { /* Code here */ };
addEventListener("load", onLoad, true);
// etc ...
}

Resources