-1

I'm trying to hack a web application that I don't own but that I wish to improve.

I'd like to add some HTML before an element in the DOM, of a same-domain iframe. Here's a sample from the console:

jQ(".x-panel-body.x-panel-body-noheader").find("iframe").contents().find("body").find("#WorkspaceFrame").find(".x-panel-tbar.x-panel-tbar-noheader").find(".x-toolbar-right").find(".x-toolbar-right-ct").find("table");
[
<table cellspacing=​"0">​…​</table>, 
<table id=​"ext-comp-1046" cellspacing=​"0" class=​"x-btn   x-btn-text-icon" style=​"width:​ auto;​">​…​</table>​, 
<table cellspacing=​"0">​…​</table>​, 
<table cellspacing=​"0">​…​</table>​, 
<table id=​"ext-comp-1067" cellspacing=​"0" class=​"x-btn   x-btn-text-icon " style=​"width:​ auto;​">​…​</table>​, 
<table id=​"ext-comp-1068" cellspacing=​"0" class=​"x-btn   x-btn-text-icon " style=​"width:​ auto;​">​…​</table>​, 
<table id=​"ext-comp-1069" cellspacing=​"0" class=​"x-btn   x-btn-text-icon" style=​"width:​ auto;​">​…​</table>​, 
<table cellspacing=​"0">​…​</table>​, 
<table cellspacing=​"0">​…​</table>​, 
<table id=​"ext-comp-1253" cellspacing=​"0" class=​"x-btn   x-btn-text-icon x-item-disabled" style=​"width:​ auto;​">​…​</table>​, 
<table id=​"ext-comp-1254" cellspacing=​"0" class=​"x-btn   x-btn-text-icon" style=​"width:​ auto;​">​…​</table>​, 
<table cellspacing=​"0">​…​</table>​, 
<table cellspacing=​"0">​…​</table>​, 
<table id=​"ext-comp-1760" cellspacing=​"0" class=​"x-btn   x-btn-text-icon" style=​"width:​ auto;​">​…​</table>​, 
<table cellspacing=​"0">​…​</table>​
]

jQ is the name of my jQuery (I can't use $).

So basically it works from the console, and saveButton contains an array. Here's my script:

saveButton=jQ(".x-panel-body.x-panel-body-noheader").find("iframe").contents().find("body").find("#WorkspaceFrame").find(".x-panel-tbar.x-panel-tbar-noheader").find(".x-toolbar-right").find(".x-toolbar-right-ct").find("table");
console.log(saveButton);
jQ.each(saveButton, new function(i,v) { console.log("#"+i+": "+v); });

Here's the console output:

[prevObject: b.fn.b.init[0], context: document, selector: "body #WorkspaceFrame .x-panel-tbar.x-panel-tbar-noheader .x-toolbar-right .x-toolbar-right-ct table", jquery: "1.9.1", constructor: function…]
#undefined: undefined

So saveButton is an object, but not an array, and this is confirmed by the failed jQ.each.

I'd like to take one of the tables (it's always the 3rd) and call .before('<p>something</p>') on it. I tried saveButton[2].before('<p>HI</p>'), it's not working.


Here's a new try after the hint in Zoltan's answer:

jQ(".x-panel-body.x-panel-body-noheader").find("iframe").contents().find("body").find("#WorkspaceFrame").find(".x-panel-tbar.x-panel-tbar-noheader").find(".x-toolbar-right").find(".x-toolbar-right-ct").find("table").eq(8).find(">:first-child").find(">:first-child").find(">:first-child").before('<td class="x-toolbar-cell"><button>Test</button></td>');

Typing this in the console properly adds the button where I want it, however the same line in the JS code isn't working. What gives?

Brock Adams
  • 90,639
  • 22
  • 233
  • 295
Benoit Duffez
  • 11,839
  • 12
  • 77
  • 125

2 Answers2

0

You can get n-th object in a selection with .eq(n)

saveButton.eq(n).before('<p>HI</p>')

saveButton[2] is not working because this way you get a plain element without .before function, but .eq returns jQuery wrapped object and you can call jQuery functions on it.

The origin of the problem could also be that what you select is inside an iframe. When you run JS from the console you are in the outer window, and you must "step" into the iframe. But JS may run in the inner window and running the same query from there would not find any iframes, therefore returning nothing into the saveButton variable.

Zoltan.Tamasi
  • 1,382
  • 13
  • 26
0

It works in the console because you are much slower than the userscript. ;)   The userscript fires long before the iframe(s) finish loading, and that's even if the iframes don't use AJAX to set content.

The easiest way to address this is to tune your userscript to work directly on the iframe in question, that simplifies much.

However, since the <iframe> appears to be same domain, you can also use the same techniques needed for AJAX pages to handle that iframe content. In this case, the waitForKeyElements utility works great.

Here's a complete script that should work for you (but untested since I don't know the target page). Note that if you are running Chrome, you need to install Tampermonkey (which you should do anyway) to run it. :

// ==UserScript==
// @name     _Interacting with iframed content
// @include  http://YOUR_SERVER.COM/YOUR_PATH/*
// @require  http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js
// @require  https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant    GM_addStyle
// ==/UserScript==
/*- The @grant directive is needed to work around a design change
    introduced in GM 1.0.   It restores the sandbox.
*/
waitForKeyElements (
    //-- Double check this selector from the console...
    "#WorkspaceFrame .x-toolbar-right-ct table:eq(8) >:first-child >:first-child >:first-child",
    addButton, 
    false, 
    ".x-panel-body.x-panel-body-noheader iframe"
);

function addButton (jNode) {
    jNode.before ('<td class="x-toolbar-cell"><button class="gmMyButton">Test</button></td>');
}



As a bonus, you would activate your button(s) like this. (The delay is needed to avoid race conditions.):

waitForKeyElements (
    "body", 
    addButtonHandler, 
    false, 
    ".x-panel-body.x-panel-body-noheader iframe"
);

function addButtonHandler (jNode) {
    jNode.on ("click", "button.gmMyButton", myButtonHandler);
}

function myButtonHandler (evt) {
    var btn = $(evt.target);

    console.log ("You just clicked button '" + btn.text() + "'.");
}
Community
  • 1
  • 1
Brock Adams
  • 90,639
  • 22
  • 233
  • 295
  • Wow, this is reaaaally good. Thanks a lot. However I have several issues: I had code that triggers on a specific AJAX call, yours triggers on all calls and adds too many buttons. Also, the way it requires jQuery binds it to `$`, which is already used by the app for something else. I had named my jQuery `jQ` in order to avoid problems. Here's my code: https://gist.github.com/BenoitDuffez/7368097 – Benoit Duffez Nov 08 '13 at 08:38
  • Your AJAX bit is not in this question and really makes it a new question. The conditions that code uses to add the button are radically different than what's stated in the question! You should ask a new question for the AJAX wrinkle. Also, if there is some difference in the AJAX-created nodes, you can tune `addButton` and not have to intercept the page's XHR at all. ... In my answer, you can use `$` because the script scope is completely sandboxed from the page (as it should be) there is no possibility of conflict. That's one of the reasons for that `@grant` directive. – Brock Adams Nov 08 '13 at 14:16