Send DOM node object via chrome.tabs.sendMessage - javascript

I am writing a Chrome extension. I need to pass an element object from the content script to the background script.
The goal:
The extension is about record and replay user actions.
The data is saved on extension`s localstorage on different object for each tab (by tab ID).
The data structure is a list of {x: x, y:y, element: element}
When the user wants to replay, I am using a loop for each object on the list and using .click() on the element
Code in content script:
The function that sends a message to the background script:
function addToEventHistory(cords) {
console.log(cords)
chrome.runtime.sendMessage({action: "addToEventHistory", cords: cords}, function(response) {
return response;
});
}
The function that get the element and sens it:
mouseClick: function(e) {
var target = e.target || e.srcElement
var clickEvent = {x: e.pageX, y: e.pageY, element: target}
addToEventHistory(clickEvent)
}
The code in the background script:
var tabId = sender.tab.id;
var existingRecords = JSON.parse(localStorage.getItem('record_'+tabId)) || [];
existingRecords.push(request.cords)
console.log(request.cords)
localStorage.setItem('record_'+tabId, JSON.stringify(existingRecords));
sendResponse();
The problem is that the element that I am sending is recieved as an empty object. notice to the console.log on send and recieve. the outputs are:
sending:
Object {x: 1205, y: 1067, element: div#content.snippet-hidden}
receiving:
Object {x: 1205, y: 1067, element: Object}
* the element Object is empty and has only _proto_
What is the reason?
How can I solve this issue?
Looks like the issue is not serialize the DOM object, because the object looks ok right before the sending and not ok at receiving..

You can't send a DOM element as a runtime.sendMessage() message
The message in runtime.sendMessage() must be "a JSON-ifiable object". DOM elements/nodes are not JSON-ifiable. Thus, you can not send them. In your case, you are trying to send the target of the click event.
What you will need to do instead of trying to serialize the DOM element is, ultimately, determined by why you need this information in your background script.
If you want to identify the element, you will need to determine a unique selector. One way to do this would be to assign a unique ID to the element and pass that ID in your message. However, that will only be effective if you are wanting to refer to the DOM node during the time that page is loaded within that tab. Obviously, any ID you assign will not be available once the browser has left the page, or loaded it in a different tab. Thus, that alternative is only viable for identifying an element for the life of the current page. However, for an application where you were wanting to just store the actual DOM element, assigning a unique ID would be a valid solution. In other words, storing the DOM element would only be valid for the life of the page, so assigning a unique ID would be valid for the same time period (life of the current page).
If you want methods which will uniquely identify the element when the page is re-loaded, you will need to use a different method than assigning an ID. What to use will depend largely on how you are going about selecting the element when you are wanting to use it and how resilient you want the selection to be with respect to changes in the page structure (e.g. on pages where the structure is dynamic, you may need to use other methods than would work on a static page).
For your application, where you want to record and playback user actions, you will need to determine if you want to record these actions based on where the mouse is within the page, or based on the elements upon which the user initiates events. This is a common problem for applications/languages which are used to record/playback/simulate user actions. Commonly, the user is given the option as to how they want such user interaction to be recorded (e.g. by location or element). If you choose to store the user actions only by the location of the mouse at the time an event occurred, then you can use Document.elementFromPoint() to determine which element is now at that point and send the event to that element. However, when doing so, you will also need to track the scrolling state of the document and determine if you are going to store the location of the mouse based on the location of the mouse within the current display, or relative to the document.

I used a workaround to click the element,
besides save the element and then using element.click() I used the cords to click the element without saving the element itself:
document.elementFromPoint(cords.x - window.pageXOffset, cords.y - window.pageYOffset).click();

Related

Access Elements in Another Window

is it possible to write a javascript to access the elements (knowing their id) in another open window? I want to refresh the page and read some elements' contents.
You can totally use sessionStorage ! Here is the Documentation
If user direct to next page in same tab, sessionStorage can easily save you data and reuse in next page.
// set in page A
window.sessionStorage.setItem('youdata', 'youdata');
// or window.sessionStorage['youdata'] = 'youdata';
// get in page B
var youdata = window.sessionStorage.getItem('youdata');
// or var youdata = window.sessionStorage['youdata'];
That's it! very simple!
If you'll open a new tab, you can use localStorage. Here Is the Documentation
The usage of localStorage is like the way of sessionStorage.
While do saving information for other pages, these two method only need browsers' support.

Chrome Extension Notifications Not Always Created - Reuse?

Let's say I want to show the same notification each time something happens. That's what I currently use:
chrome.notifications.create(id, {
type:"basic",
title:"Title",
message:"My message",
iconUrl: "icon.png",
}, notificationResult);
But sometimes the notification doesn't appear.
Is that an id thing ? Do I need to reuse an already created notification ? Can I not create a new notification with the same id ?
I tried to do a var notification = chrome.notifications.create(id .... ) and do a notification.show() in case I already created one with the same id but that also didn't solve it.
So - do I need to recreate an existing notification each time I want to show the same one (which currently doesn't work for me), or is there a different way? How to make sure it pops every time?
The id in the create function is specifically for reusing. IDs must be unique. If you use create with an ID of an existing notification, it basically behaves like an update.
If a notification exists, it may no longer be shown but only be visible in the Message Center. In this case, the notification IS updated - but not shown again.
The API docs specify that you can pass an empty string to the notification to get a unique new id. If you need it, it is passed to the callback.
But if you do want to reuse the ID (ensuring that the notification is unique), you can use priority trick to make it show again.
You can clear the notification if its not use and if you want to use the same id.
For example :
function Notify(){
var my_notif_id="some_id";
//This will clear your previous notifcation with the same ID
chrome.notifications.clear(my_notif_id,function(){});
chrome.notifications.create(my_notif_id,options,function(){});
}
Now each time you call the notify function to display notification it will clear the old notification before displaying new notification and gets displayed.
UPDATED
As #Xan suggested, Its good to incorporate the create() method inside callback function of clear()
So here is the complete example :
function Notify(id, options){
//This will clear your previous notifcation with the same ID
chrome.notifications.clear(id, function() {
//inside callback function
chrome.notifications.create(id, options, function(){});
});
}

Lightswitch - Passing Paramaters in the HTML Client

ive been trying for ages now to pass the value with little success, the image below is what I am aiming for:
and this is the table structure/relationships
cheers for any help you can give me guys, this is beginning to be a real pain in the arse
based on this I managed to solve my issue by doing the following
LightSwitch: Passing data from one screen to another -> for Desktop Client
For HTML Client
You MUST have a parameter on the screen from which you are passing the data, this is done by creating a data item, i.e. int in the below example, and in the post render code for this use the following code:
myapp.ViewDeliveryNote.DeliveryIDPass_postRender = function (element, contentItem) {
contentItem.screen.DeliveryIDPass = //created parameter
contentItem.screen.DeliveryNote.DeliveryID; //the unique ID from the screen
};
On the screen you want to pass to, add a new data item as that datatype OR if you are using it as a search parameter, use this.
Find the parameter/data item you added on the left hand panel and click on the item, now in the properties window tick (is parameter)
If there was a previous link between the pages via a button, remove the on tap and re-add it... you will now see an additional box where the application is asking for the value to pass, select the one you want and that shoud work :)
hope this helps

storing a variable in localStorage is too slow

I have two jQuery mobile pages (#list and #show). There are several items on the #list page with different IDs. If I click on item no.5, the ID no5 will be stored in localStorage and I will be redirected to page #show
Now the problem:
Storing the ID in localStorage works, but the next page shows me not the item no.5, but it shows me an old item, that was in the localStorage before.
script from page #list
localStorage.setItem("garageID", $(this).attr('id'));
window.location.replace("#show");
I encountered this problem too (and not on a mobile : on Chromium/linux).
As there doesn't seem to be a callback based API, I "fixed" it with a timeout which "prevents" the page to be closed before the setItem action is done :
localStorage.setItem(name, value);
setTimeout(function(){
// change location
}, 50);
A timeout of 0 might be enough but as I didn't find any specification (it's probably in the realm of bugs) and the problem isn't consistently reproduced I didn't take any chance. If you want you might test in a loop :
function setLocalStorageAndLeave(name, value, newLocation){
value = value.toString(); // to prevent infinite loops
localStorage.setItem(name, value);
(function one(){
if (localStorage.getItem(name) === value) {
window.location = newLocation;
} else {
setTimeout(one, 30);
}
})();
}
But I don't see how the fact that localStorage.getItem returns the right value would guarantee it's really written in a permanent way as there's no specification of the interruptable behavior, I don't know if the following part of the spec can be legitimately interpreted as meaning the browser is allowed to forget about dumping on disk when it leaves the page :
This specification does not require that the above methods wait until
the data has been physically written to disk. Only consistency in what
different scripts accessing the same underlying list of key/value
pairs see is required.
In your precise case, a solution might be to simply scroll to the element with that given name to avoid changing page.
Note on the presumed bug :
I didn't find nor fill any bug report as I find it hard to reproduce. In the cases I observed on Chromium/linux it happened with the delete operation.
Disclaimer: This solution isn't official and only tested for demo, not for production.
You can pass data between pages using $.mobile.changePage("target", { data: "anything" });. However, it only works when target is a URL (aka single page model).
Nevertheless, you still can pass data between pages - even if you're using Multi-page model - but you need to retrieve it manually.
When page is changed, it goes through several stages, one of them is pagebeforechange. That event carries two objects event and data. The latter object holds all details related to the page you're moving from and the page you're going to.
Since $.mobile.changePage() would ignore passed parameters on Multi-page model, you need to push your own property into data.options object through $.mobile.changePage("#", { options }) and then retrieve it when pagebeforechange is triggered. This way you won't need localstorage nor will you need callbacks or setTimeout.
Step one:
Pass data upon changing page. Use a unique property in order not to conflict with jQM ones. I have used stuff.
/* jQM <= v1.3.2 */
$.mobile.changePage("#page", { stuff: "id-123" });
/* jQM >= v1.4.0 */
$.mobile.pageContainer.pagecontainer("change", "#page", { stuff: "id-123" });
Step two:
Retrieve data when pagebeforechange is triggered on the page you're moving to, in your case #show.
$(document).on("pagebeforechange", function (event, data) {
/* check if page to be shown is #show */
if (data.toPage[0].id == "show") {
/* retrieve .stuff from data.options object */
var stuff = data.options.stuff;
/* returns id-123 */
console.log(stuff);
}
});
Demo

How can I change the TargetControlID of AutoCompleteExtender client side (javascript)?

Normally, I can can set the TargetControlID server side using something like this:
AutoCompleteExtender ace = new AutoCompleteExtender();
ace.ID = "AutoCompleteExtender1";
ace.TargetControlID = "whatever";
I know how to grab the AutoCompleteExtender client side, but I am looking for a method to update the TargetControlID client side too. Any ideas?
Well sadly this is not possible for existing instance of AutoCompleteExtender. There are a few methods that you might be interested in like below
var x = $find("AutocompleteBehaviorID");//find the instance
x.get_completionListElementID();//get the ID of target textbox
x.set_completionListElementID();//set the ID of target textbox has no effect though :(
x._completionListElement();//direct access to DOM element that acts as target
the problem here seems initialized version attaches additional events to target textbox during init phase of the control toolkit( yeah client side too has a init phase). When a initialized version is made to change as the target (as you wanted to do) then these events keypress,blur etc are not added hence you don't see any changes. But if you knew javascript you can do the below to make it work with any textbox.
$create(Behavior,{properties},{events},interfaces,target);
where
Behavior
AjaxControlToolkit.AutoCompleteBehavior
properties
is a javascript object as below (there are more properties but these suffice
{
"completionInterval": 1,
"completionListElementID": "empty panel id",
"completionListItemCssClass": "css class name",
"delimiterCharacters": ";",
"highlightedItemCssClass": "css class name",
"id": "CLIENTSIDEID",
"minimumPrefixLength": 1,
"serviceMethod": "WebMethodName",
"servicePath": "AbsolutePath to asmx file"
}
Events
there are more events available
{
"itemSelected": jsFn,
"populated": jsFn
}
The Target
Target element is the most important. It is this text box that all the events ,bells and whistles attracted to.
$get("ELEMENT ID")
now that is all over , you can initialize a instance of auto complete though javascript all the time. Just make sure the ID already does not exist.
Apparently Microsoft didn't think was important, so there is not a way to do this at this time :)

Categories