xpcom/jetpack observe all document loads - firefox-addon

I write a Mozilla Jetpack based add-on that has to run whenever a document is loaded. For "toplevel documents" this mostly works using this code (OserverService = require('observer-service')):
this.endDocumentLoadCallback = function (subject, data) {
console.log('loaded: '+subject.location);
try {
server.onEndDocumentLoad(subject);
}
catch (e) {
console.error(formatTraceback(e));
}
};
ObserverService.add("EndDocumentLoad", this.endDocumentLoadCallback);
But the callback doesn't get called when the user opens a new tab using middle click or (more importantly!) for frames. And even this topic I only got through reading the source of another extension and not through the documentation.
So how do I register a callback that really gets called every time a document is loaded?
Edit: This seems to do what I want:
function callback (event) {
// this is the content document of the loaded page.
var doc = event.originalTarget;
if (doc instanceof Ci.nsIDOMNSHTMLDocument) {
// is this an inner frame?
if (doc.defaultView.frameElement) {
// Frame within a tab was loaded.
console.log('!!! loaded frame:',doc.location.href);
}
else {
console.log('!!! loaded top level document:',doc.location.href);
}
}
}
var wm = Cc["#mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
var mainWindow = wm.getMostRecentWindow("navigator:browser");
mainWindow.gBrowser.addEventListener("load", callback, true);
Got it partially from here: https://developer.mozilla.org/en/XUL_School/Intercepting_Page_Loads

#kizzx2 you are better served with #jetpack
To the original question: why don't you use tab-browser module. Something like this:
var browser = require("tab-browser");
exports.main = function main(options, callbacks) {
initialize(function (config) {
browser.whenContentLoaded(
function(window) {
// something to do with the window
// e.g., if (window.locations.href === "something")
}
);
});
Much cleaner than what you do IMHO and (until we have official pageMods module) the supported way how to do this.

As of Addon SDK 1.0, the proper way to do this is to use the page-mod module.
(Under the hood it's implemented using the document-element-inserted observer service notification, you can use it in a regular extension or if page-mod doesn't suit you.)

Related

Electron Web Bluetooth Device selection returning only 1 device

have found that in my electron application the following code from the main.js only returns a device list of length 1 (filled with one device) even though there are many devices around.
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault();
console.log(deviceList);
bluetoothSelection.selectBluetoothDevice(deviceList, mainWindow, (deviceId) => {
callback(deviceId);
});
If I call
navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: [serviceUuid]
})
multiple times, the device returned changes and if I cycle through it often enough, I get the device I want eventually.. But I built a device Picker window and all that stuff and now the window opens for only one device, which makes everything quite annoying:P
Any ideas on what could cause this or even how I could fix it?
If you have a look at https://www.electronjs.org/docs/latest/api/web-contents#event-select-bluetooth-device you will find the example code provided by electron you probably already know:
win.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault()
const result = deviceList.find((device) => {
return device.deviceName === "test"
})
if (!result) {
callback('')
} else {
callback(result.deviceId)
}
})
You have to prevent the callback until you have found the device you are looking for. I suggest to open a second window and pass in the deviceList. Now you can display the devices and let the user choose one. If the user has chosen a device, you can close the second window and call the callback with this deviceId.
To communicate between the windows you can use the “contextBridge” with “ipcRenderer” and “ipcMain” and to call the callback you can make a global variable
var callbackForBluetoothEvent = null;
and fill it int the
mainWindow.webContents.on(
// stuff
callbackForBluetoothEvent = callback; //to make it accessible outside
// stuff
);
With a “ipcMain.on”
ipcMain.on("BLEScannFinished", (event, args) => {
console.log(args);
console.log(BLEDevicesList.find((item) => item.deviceId === args));
let BLEDevicesChoosen = BLEDevicesList.find((item) => item.deviceId === args);
callbackForBluetoothEvent(BLEDevicesChoosen.deviceId);
});
You can than call the callback
Unfortunately this is a bit to much code for a forum post but you can find a rudimentary solution of this suggestion at the link:
https://github.com/grosdode/Electron-multible-BLE-devices
The electron issues 11865 was also helpful and there is a page which shows alternative code for the suggested solution. Unfortunately also to much code to post it here.
https://technoteshelp.com/electron-web-bluetooth-api-requestdevice-error/

Set timeout for notifications.notify FireFox Addon SDK

Please help me with Notification in my Firefox add-on.
var notifications = require("sdk/notifications");
function showNotifcation(title, text) {
notifications.notify({
iconURL: data.url("img/icon.png"),
title: title,
text: text
});
setTimeout(notifications.close(), 1000);
}
Not work.
Without more information from you it is not possible to be sure as to what your problem/issue is.
However, a brief look at the sdk/notifications documentation, and source code, indicates that you are attempting to use a non-existent method: notifications.close(). There is no such method in sdk/notifications.
One possible reason for your attempt to use this method is that you are conflating the Web Notification API, more detail, with the Add-on SDK sdk/notifications.
The Add-on SDK, sdk/notifications, has no way for you to programmatically close the notification from your code. Thus, there is no way for you to set a timeout for the notification using this interface. However, in some operating systems/windowing systems there is already a default timeout for these notifications.
You will need to either display a panel on your own, or use the chrome interfaces described in User Notifications and Alerts.
In addition, it would be unusual for you to be able to just call setTimeout(). That will, under most contexts, not be defined. You would normally need to use sdk/timers with:
var { setTimeout } = require("sdk/timers");
In some contexts, you might be able to use window.setTimeout(), when window is appropriately defined (which you will probably have to set yourself).
Modifying the code from my answer to Prevent XUL notificationBox from closing when button is hit (if you want buttons, that answer will show you how to do it), and other answers of mine: Something along the lines of what I believe you desire would be (code for the timeout is at the bottom):
function showNotificationBox(text) {
//Create some common variables if they do not exist.
if (window === null || typeof window !== "object") {
// Add/remove a "/" to comment/un-comment the code appropriate for your add-on:
//* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//*/
}
if (typeof gBrowser === "undefined") {
var gBrowser = window.gBrowser;
}
let notifyBox = gBrowser.getNotificationBox();
//appendNotification( label , value , image (URL) , priority , buttons, eventCallback )
let theNotification = notifyBox.appendNotification(text, "Test notification unique ID",
"chrome://browser/content/aboutRobots-icon.png",
notifyBox.PRIORITY_INFO_HIGH, [], null);
//* Add-on SDK:
var { setTimeout } = require("sdk/timers");
setTimeout(theNotification.close(), 10000);
//*/
/* Overlay and bootstrap:
let timerCallback = {
notify:function notify() {theNotification.close(); }
}
let closeNotificationTimer = Components.classes["#mozilla.org/timer;1"]
.createInstance(Components.interfaces.nsITimer);
closeNotificationTimer.initWithCallback(timerCallback,10000,
Components.interfaces.nsITimer.TYPE_ONE_SHOT);
//*/
}
Note: I changed the timeout to 10 seconds from the 1 second which is in the code in your question. One second is a unreasonable amount of time to expect to show anything which you actually desire the user to see and understand.
The above implements the user notification in a notificationBox. As such it shows up within the Firefox window:
It is also possible to use the nsIAlertsService which is what sdk/notifications uses. This will normally display an alert box in the bottom right of the screen, potentially outside of the Firefox window (see image on nsIAlertsService for example). The notification may show up elsewhere depending on how you have your windowing system set up (this is OS dependent). However, the documentation did not have a method to clear the notification, or set a timeout. However, the interface definition does show that a closeAlert() method does exist. The source code for the sdk/notifications does not expose this to the Add-on SDK. Thus, you would need to use the chrome interfaces. I have updated the documentation to show closeAlert().
Such as (some code taken and modified from nsIAlertsService):
//* Add-on SDK:
var {Cc, Ci} = require("chrome");
//*/
/* Overlay and bootstrap:
const Cc = Components.classes;
const Ci = Components.interfaces;
//*/
function showNotifcation(title, text) {
var alertsService = Cc["#mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
try {
//The second use of title is the alert name.
alertsService.showAlertNotification(icon, title, text, false, "", null, title);
} catch (e) {
// This can fail on Mac OS X
}
//* Add-on SDK:
var { setTimeout } = require("sdk/timers");
setTimeout(alertsService.closeAlert(title), 10000);
//*/
/* Overlay and bootstrap:
let alertTimerCallback = {
notify:function notify() {alertsService.closeAlert(title); }
}
let closeAlertTimer = Cc["#mozilla.org/timer;1"].createInstance(Components.interfaces
.nsITimer);
closeAlertTimer.initWithCallback(alertTimerCallback,10000,Ci.nsITimer.TYPE_ONE_SHOT);
//*/
}
I have only tested the above code with a bootstrapped/restartless Firefox add-on. Thus, the Add-on SDK code may be slightly off.

Stop the back history, juste close panel [duplicate]

I have a jQuery mobile panel which slides in from the side, it works great.
But lets say you have a login page, that redirects to a main page with a panel. Now if the user opens the panel, and then clicks the back button, he expects the panel to close. But instead the browser navigates back to the login page.
I´ve tried adding something to the url:
window.location.hash = "panelOpen";
But that just messes up the jQuery mobile history state pattern. I´ve also tried to listen to the navigate event, and prevent it if a panel is open:
$(window).on('navigate', function (e, hans) {
var panels = $('[data-role="panel"].ui-panel-open');
if (panels&&panels.length>0) {
e.preventDefault();
e.stopPropagation();
$('#' + panels[0].id).panel('close');
return false;
}
});
This kind of works, except that the url is changed, and I cannot grab the event that changes the url. Furthermore, it also messes up the jQuery mobile history pattern.
So how does people achieve this expected 'app-like' behaviour with a jQuery mobile panel; open panel > history back > close panel. And thats it.
Thanks alot!
Updated
Instead of retrieving current URL from jQuery Mobile's history, It is safer to retrieve it from hashchange event event.originalEvent.newURL and then pass it to popstate event to be replaceState() with that URL.
Instead of listening to navigate, listen to popstate which fires before. The trick here is manipulate both browser's history and jQuery Mobile's history by replaceState() and reload same page without transition.
var newUrl;
$(window).on("hashchange", function (e) {
/* retrieve URL */
newUrl = e.originalEvent.newURL;
}).on("popstate", function (e) {
var direction = e.historyState.direction == "back" ? true : false,
activePanel = $(".ui-panel-open").length > 0 ? true : false,
url = newUrl,
title = document.title;
if (direction && activePanel) {
$(".ui-panel-open").panel("close");
$(".ui-header .ui-btn-active").removeClass("ui-btn-active");
/* reload same page to maintain jQM's history */
$.mobile.pageContainer.pagecontainer("change", url, {
allowSamePageTransition: true
});
/* replace state to maintain browsers history */
window.history.replaceState({}, title, url);
/* prevent navigating into history */
return false;
}
});
This part is meant to maintain same transition used previously as transition is set to none when reloading same page.
$(document).on("pagebeforechange", function (e, data) {
if (data.options && data.options.allowSamePageTransition) {
data.options.transition = "none";
} else {
data.options.transition = $.mobile.defaultPageTransition;
}
});
Demo - Code
I am a little bit late on the party, but i had recently the same requirements and i would like to share how i did it. So, i extended the requirement in the original question to Panels, Popups and Pages:
...an expected 'app-like' behaviour, history back > close
whaterver is open. And thats it.
In .on("panelopen"), .on("popupafteropen") and .on("pagecontainershow") i simply add another entry to the window history, by using the HTML5 API (https://developer.mozilla.org/en-US/docs/Web/API/History_API) (I believe there is no need to use the JQM navigate browser quirks for that):
window.history.pushState({}, window.document.title, window.location.href);
After that, i'm using more or less Omar's function to intercept the popstate event:
$(window).on("popstate", function (e) {
var pageId = $(":mobile-pagecontainer").pagecontainer("getActivePage").prop("id");
var pageOpen = (pageId != "page-home");
var panelOpen = $(".ui-panel-open").length > 0;
var popupOpen = $(".ui-popup-active").length > 0;
if(pageOpen) {
$.mobile.pageContainer.pagecontainer("change", "#page-home", {reverse: true});
return false;
}
if(panelOpen) {
$(".ui-panel-open").panel("close");
return false;
}
if(popupOpen) {
$(".ui-popup-active .ui-popup").popup("close")
return false;
}
});
As you see, the is just only one level to the home-page, but this can be easily extended by using JQM history implementation to get the previous page:
var activeId = $.mobile.navigate.history.activeIndex;
var jqmHistory = $.mobile.navigate.history.stack; // array of pages
and use pagecontainer to change to the active entry - 1.
As last note, this works well also by completely disabling the built-in JQM Ajax navigation system:
/* Completely disable navigation for mobile app */
$.mobile.ajaxEnabled = false;
$.mobile.loadingMessage = false;
$.mobile.pushStateEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.changePage.defaults.changeHash = false;
$.mobile.popup.prototype.options.history = false;
(Tested in Browser, on real Android and iOS devices)

Is there a way to rearrange objects on a DevX report?

I want to enable user to move things around on a devexpress print preview and print it only after it is done. If it is possible, could I get some directions where I can start looking? (I will not have the time to look into the whole documentation, what may sound lazy, but devx is kinda huge for the short time I have.)
I don't think you could do this on the Print preview directly, but what you could do is provide a button which launches the XtraReports Designer and pass in the layout from your currently displayed document. When the user has finished editing then you can reload the document in the print preview, loading its new layout as required. You may need to customize the designer heavily to remove various options restricting the user to only editing certain aspects - you can hide much of the functionality including data source, component tray etc:
designer video
designer documentation
hide options in designer
if(EditLayout(document))
RefreshDocument();
public static bool EditLayout(XtraReport document)
{
using (var designer = new XRDesignRibbonForm())
{
designer.OpenReport(document);
XRDesignPanel activePanel = designer.ActiveDesignPanel;
activePanel.AddCommandHandler(new DesignerCommandHandler(activePanel));
HideDesignerOptions(activePanel);
designer.ShowDialog();
changesMade = activePanel.Tag != null && (DialogResult)activePanel.Tag == DialogResult.Yes; //set this tag in your DesignerCommandHandler
activePanel.CloseReport();
}
return changesMade;
}
Finally, some utility methods for changing a document/reports layout:
internal static byte[] GetLayoutData(this XtraReport report)
{
using (MemoryStream mem = new MemoryStream())
{
report.SaveLayoutToXml(mem);
return mem.ToArray();
}
}
internal static void SetLayoutData(this XtraReport report, byte[] data)
{
using (var mem = new MemoryStream(data))
{
report.LoadLayoutFromXml(mem);
}
}

Mootools The Wall + Slimbox

I am using wall.plasm.it and would like to use slimbox to open the images in fullscreen http://www.digitalia.be/software/slimbox
But when I add the slimbox script to my website and click on an image, it just opens the image and redirects me to the image on the server but doesnt start slimbox
Ah, so it seems you would have to do something like the following:
Use wall.setCallOnUpdate( myFunctionUpdate ); (this) to update all newly generated images so that they have the rel=lightbox attribute set.
Reinitialize slimbox.
--OR--
You could simply use the CallOnUpdate to directly enable the slimbox on items using the API
I think you need to use Event Delegation - this is how I got my wall working with lightbox. I called this after the initWall() method and also the changeOnUpdate() so the new events would be added.
var boundClicker;
$$('div.tile a').each(function(button) {
var linkDest = button.get('href');
var title = button.get('data-title');
var type = button.get('rel');
var clicker = function(event)
{
event.stop();
Mediabox.open(linkDest, title, '470 290');
};
boundClicker = clicker.bind(button);
button.addEvent('click', boundClicker);
});
Add a "click" event handler and use the getMovement function.
items.each(function(item) {
item.addEvent("click",function(e){
if( wall.getMovement() ){
e.stop();
}
else {
Mediabox.open(...);
}
}); }

Resources