0

I'm trying to access inside a TradingView stock chart widget (https://www.tradingview.com/widget/advanced-chart/), which loads in an iframe. The goal is to programatically draw on the canvas tag of the widget after it has loaded, but I'm getting the following error:

Blocked a frame with origin "http://localhost:3000" from accessing a cross-origin frame

I tried basically every answer here: How to enable cors nodejs with express?, but I think this might be a different context than trying to access an iframe. I saw some other information about using postMessage, but I also don't think that's relevant for interacting with HTML elements.

Here's the full code. It's the last line within the draw function that creates the error.

index.js

const cors = require('cors');
const express = require('express');
const app = express();

app.use(cors())


app.get('/', (req, res, next) => {
    res.sendFile(__dirname + '/index.html');
});

app.get('/test', (req, res, next) => {
    res.send("success")
});


const port = 3000
app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

index.html

<html>
</script>
<!-- TradingView Widget BEGIN -->
<div class="tradingview-widget-container">
  <div id="technical-analysis-chart-demo"></div>
  <div class="tradingview-widget-copyright"><a href="https://www.tradingview.com/symbols/AAPL/" rel="noopener" target="_blank"><span class="blue-text">AAPL stock chart</span></a> by TradingView</div>
  <script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
  <script type="text/javascript">
  new TradingView.widget(
  {
  "container_id": "technical-analysis-chart-demo",
  "width": "100%",
  "height": "100%",
  "autosize": true,
  "symbol": "AAPL",
  "interval": "D",
  "timezone": "exchange",
  "theme": "light",
  "style": "1",
  "toolbar_bg": "#f1f3f6",
  "withdateranges": true,
  "hide_side_toolbar": false,
  "allow_symbol_change": true,
  "save_image": false,
  "studies": [
    "ROC@tv-basicstudies",
    "StochasticRSI@tv-basicstudies",
    "MASimple@tv-basicstudies"
  ],
  "show_popup_button": true,
  "popup_width": "1000",
  "popup_height": "650",
  "locale": "en"
}
  );
  </script>
</div>
<!-- TradingView Widget END -->

<script>

    function draw() {

        var elem = document.querySelector('#technical-analysis-chart-demo > div > div > iframe');
        console.log(elem.contentWindow)
        console.log(elem.contentWindow.querySelector("div"))
    }

    window.setTimeout(draw, 3000);
    
</script>script>

</html>

I'm wondering if there's a workaround to this. If not, I'm wondering if I could overlay another canvas on top of the widget but still allow the user to interact with the widget at the same time.

SuperCodeBrah
  • 2,874
  • 2
  • 19
  • 33
  • See https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#cross-origin_script_api_access – Heiko Theißen Mar 20 '23 at 14:32
  • CORS applies to HTTP requests, that is not your issue here. You are fighting the good old Same-Origin-Policy. _"wondering if I could overlay another canvas on top of the widget but still allow the user to interact with the widget at the same time"_ - if they don't need to interact with _your_ canvas, then you should be able to use `pointer-events: none` to let everything "go right through." – CBroe Mar 20 '23 at 14:38
  • "I saw some other information about using postMessage, but I also don't think that's relevant for interacting with HTML elements." — It's highly relevant. `postMessage` is how you communicate between frames on different origins. You can't access the DOM of a cross-origin frame directly, you can only post a message to the frame, have JS running in that frame access the DOM and then send a message back (again using `postMessage`). – Quentin Mar 20 '23 at 14:41
  • @CBroe - The only issue I can think of is that TradingView charts can be dragged to go further back in time, which would cause the overlay to become misaligned with underlying chart widget. Ideally, the user could interact with both the overlay and the widget at the same time. – SuperCodeBrah Mar 20 '23 at 14:48
  • No, that won't be possible. – CBroe Mar 20 '23 at 15:01
  • @Quentin - This line of code results in an error before I even get to the `postMessage` stage: `document.querySelector('iframe').contentWindow.addEventListener('message', () => {});` – SuperCodeBrah Mar 20 '23 at 15:26
  • @SuperCodeBrah — I said "You can't access the DOM of a cross-origin frame directly, you can only post a message to the frame" — Since you can't access the DOM, you can't add the event listener across origins. The page *in the frame* has to include the code that listens for the message. – Quentin Mar 20 '23 at 15:28
  • I guess I should have been more clear in the question, but that's what I was getting at. I don't control the iframe, so `postMessage` doesn't help me. – SuperCodeBrah Mar 20 '23 at 15:44
  • @SuperCodeBrah — Let's put it this way. You want to edit a webpage, on a site belonging to someone else, which you do not control. It is not reasonable for a browser to provide a way for you to do that. It isn't your site! – Quentin Mar 20 '23 at 17:19

1 Answers1

0

This appears to be a CSP issue, they have specified which domains may interact in which way and your inline script is not allowed to interact with the iframe. So this solution is not possible.

I am not that familiar with iframes to answer the question if you can use an overlay and pass the event down to the iframe. I would probably look into where the data comes from and if I can get that data via an endpoint which does not have any problem with being called cross domain and make this dashboard myself.

Merlijn
  • 64
  • 5
  • "This appears to be a CSP issue" — It isn't. Cross-origin frame access outside of postMessage is impossible. A CSP couldn't tighten security any further. – Quentin Mar 20 '23 at 14:42
  • "if you can use an overlay and pass the event down to the iframe" — This sounds like you are suggesting using a [clickjacking attack](https://en.wikipedia.org/wiki/Clickjacking). That's a really bad idea, and browsers do try to defend against them. – Quentin Mar 20 '23 at 14:42