0
votes

I'm working on a Chrome Extension. For now, there is one content script which returns an XPath of clicked element. Another content script adds an iframe into the current html page. In this iframe there is a textarea field which should show the xpath of the element when user clicks on it.

Unfortunately it does not work. The xpath is correctly generated (tested using console.log(path) but the command:

$('#xh-bar').contents().find('#product-path').val('some_val');

Doesn't alter the textarea. Do you know where is the problem? I've tried to change the order of bar.js and xpathget.js but it did not help.

manifest.json

{
  "manifest_version": 2,
  "name": "Product",
  "description": "This is a plugin collaborating with product.com",
  "version": "1.0",
  "browser_action": {
    "default_icon": "spy-icon.png",
    "default_popup": "popup.html",
    "default_title": "Click here!"
  },
  "icons":{
    "64":"spy-icon.png"
  },
  "background": {
    "scripts": ["authentication.js"]
  },
    "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["jquery-3.1.1.min.js","xpathget.js","bar.js"]
    }
  ],
  "permissions": [
    "activeTab",
    "https://ajax.googleapis.com/",
    "cookies",
    "<all_urls>"
  ],
  "web_accessible_resources": [
    "bar.html",
  ],
  "content_security_policy": "script-src 'self' https://ajax.googleapis.com; object-src 'self'"
}

xpathget.js

document.onclick = function (event) {
    if (event === undefined) event = window.event;                     // IE hack
    var target = 'target' in event ? event.target : event.srcElement; // another IE hack

    var root = document.compatMode === 'CSS1Compat' ? document.documentElement : document.body;
    var mxy = [event.clientX + root.scrollLeft, event.clientY + root.scrollTop];

    var path = getPathTo(target);
    var txy = getPageXY(target);

    alert(path);
    $('#xh-bar').contents().find('#product-spy-price').val('some_val'); # THIS SHOULD ALTER THE TEXTAREA
}

function getPathTo(element) {
    if (element.id !== '')
        return 'id("' + element.id + '")';
    if (element === document.body)
        return element.tagName;

    var ix = 0;
    var siblings = element.parentNode.childNodes;
    for (var i = 0; i < siblings.length; i++) {
        var sibling = siblings[i];
        if (sibling === element)
            return getPathTo(element.parentNode) + '/' + element.tagName + '[' + (ix + 1) + ']';
        if (sibling.nodeType === 1 && sibling.tagName === element.tagName)
            ix++;
    }
}

function getPageXY(element) {
    var x = 0, y = 0;
    while (element) {
        x += element.offsetLeft;
        y += element.offsetTop;
        element = element.offsetParent;
    }
    return [x, y];
}

bar.js

$(document).ready(function () {
    $('body').append('<iframe src="chrome-extension://some_id/bar.html" id="xh-bar" class=""></iframe>');

});

And the generated iframe is:

<iframe src="chrome-extension://haifbndknpepdhjlcnmpoemlmomnidpe/bar.html" id="xh-bar" class="">HERE IS THE BAR.HTML</iframe>

This is the bar.html in case it helps:

<!doctype html>
<html>
<head>
    <title>Product Client</title>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
    <script src="bootstrap/bootstrap.min.js"></script>
    <script src="bootstrap/html5shiv.js"></script>
    <script src="bootstrap/respond.min.js"></script>
    <script src="bootstrap/usebootstrap.js"></script>

    <script src="popup.js"></script>
    <script src="productspy.js"></script>
    <script src="xpathget.js"></script>
    <script src="jsplugins/jquery.cookie.js"></script>
    <script src="authentication.js"></script>

    <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css">

    <link href="theme/bootstrap.css" rel="stylesheet">
    <link href="theme/usebootstrap.css" rel="stylesheet">
</head>
<body>
<div class="smaller">
    <h1>Product Client - Select the product price</h1>
    <hr>
    <form class="form-horizontal">
        <fieldset>

            <!-- Form Name -->
            <legend>Form Name</legend>

            <!-- Text input-->
            <div class="form-group">
                <label class="col-md-4 control-label" for="textinput">Product Name</label>
                <div class="col-md-4">
                    <input id="textinput" name="textinput" type="text" placeholder="Nissan Patrol"
                           class="form-control input-md" required="">

                </div>
            </div>

            <!-- Textarea -->
            <div class="form-group">
                <label class="col-md-4 control-label" for="price">Price</label>
                <div class="col-md-4">
                    <textarea class="form-control" id="product-spy-price" name="price" readonly>This will show the price</textarea>
                </div>
            </div>

            <!-- Button -->
            <div class="form-group">
                <label class="col-md-4 control-label" for="submit">Send product</label>
                <div class="col-md-4">
                    <button id="submit" name="submit" class="btn btn-primary">Send</button>
                </div>
            </div>

        </fieldset>
    </form>


</div>
</body>
</html>

This is in console after: console.log($('#xh-bar').contents());

Uncaught SecurityError: Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Blocked a frame with origin "http://stackoverflow.com" from accessing a frame with origin "chrome-extension://haifbndknpepdhjlcnmpoemlmomnidpe". The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "chrome-extension". Protocols must match.

1
my guess is might be 'same origin policy' problem. what does $('#xh-bar').contents() return when logged to console?charlietfl
@charlietfl Tried to log it and it returned Ucaught SecurityError - I've added whole error at the bottom of the question. I tried it on SO.Milano
Basically the same as 'same origin policy'. You will need a different approach than that doesn't require accessing iframe. Might be able to use postMessage API. Also look into setting document.domain in the iframecharlietfl

1 Answers

3
votes

As it already answered above - you can not access to iFrame's DOM from page loaded from different than iFrame's URL because of Some Origin Policy.

But you can send message to your iFrame and process it there for setting value of textarea.

You can do it bu that way:

Send message from xpathget.js

...
// Replace your setter
// $('#xh-bar').contents().find('#product-spy-price').val('some_val'); //# THIS SHOULD ALTER THE TEXTAREA
// with
var win = document.getElementById("xh-bar").contentWindow
win.postMessage(
    {"command":"click","value":path},
    chrome.extension.getURL('')
);
...

In bar.html include js file, which listens your message and sets textarea value:

...
function listener(event){
  if("command" in event.data && event.data.command=='click')
  {
      console.log(document.getElementById('product-spy-price').value=event.data.value);
  }
}

if (window.addEventListener){
  addEventListener("message", listener, false)
} else {
  attachEvent("onmessage", listener)
}
...

That's all :)

P.S> It's better to get path to your chrome pages with chrome.extension.getURL('bar.html') instead of hardcoding it - in this case you can change application ID without problems and avoid many and many troubles while support your application.