Giter Site home page Giter Site logo

Comments (34)

jerblack avatar jerblack commented on May 14, 2024 10

Targetting the iframe appears to be just a matter of getting a new context that is pointing at the iframe target. In Chromedp each page can have several targets. Each target has a type ("page" for the main page, "background_page" for Chrome extensions, and "iframe" for iframes).

You can get a list of the page targets by calling chromedp.Targets with your main context ctx, and this will return an array of *target.Info objects. The important fields in those objects are Type, Url, and TargetID. Since you may have multiple iframes on a single page, you will want to iterate through all the targets and get the one with Type iframe and a matching Url. Once you have that, you can use the TargetID to create a new context that targets the iframe.

You can create a new context from a parent context that will let you target subtargets within a page as long as you specify the ContextOption chromedp.WithTargetId along with the TargetID for the frame you are after. From the documentation, it looks like when you call chromedp.NewContext with a parent context but no ContextOption, it just creates a new tab in the same browser, but including WithTargetId when you call NewContext will let you access targets in the already loaded page.

Once you have the new context pointing at the iframe, you would use it just like you would the parent context.

Here's how I am getting an iframe context:

func getIframeContext(ctx context.Context, uriPart string) context.Context {
	targets, _ := chromedp.Targets(ctx)
	var tgt *target.Info
	for _, t := range targets {
		fmt.Println(t.Title, "|", t.Type, "|", t.URL, "|", t.TargetID)
		if t.Type == "iframe" && strings.Contains(t.URL, uriPart) {
			tgt = t
		}
	}
	if tgt != nil {
		ictx, _ := chromedp.NewContext(ctx, chromedp.WithTargetID(tgt.TargetID))
		return ictx
	}
	return nil
}

And here's how you would use it.

ictx := getIframeContext(ctx, "/substring/in/iframe/uri/")
selector := "a.LinkInIframe"
script := fmt.Sprintf("document.querySelector(\"%s\").href;", selector)
var b []byte
_ = chromedp.Run(
	ictx, // <-- instead of ctx
	chromedp.WaitVisible(selector, chromedp.ByQuery),
	chromedp.Evaluate(script, &b),
)
fmt.Println("href in iframe:", string(b))

Also, the --disable-web-security flag is NOT required for this to work.

from chromedp.

mvdan avatar mvdan commented on May 14, 2024 5

We're working on a redesigned version of chromedp; among many changes and improvements, it will have proper support for iframes. See the refactor branch if you want to take a peek. It should be ready soon, but still needs quite a bit of work to be usable.

from chromedp.

zhangguanzhang avatar zhangguanzhang commented on May 14, 2024 5

@ysmood nice project, I decide to use rod instead of chromedp

from chromedp.

ysmood avatar ysmood commented on May 14, 2024 4

Yes, the basics work, but some of the extras like Click and WaitVisible do not

If you can't even click the element inside an iframe, I feel it's better to say "basically doesn't work".

Maybe you can borrow some ideas of how rod handles it, you can do all the page operations with iframe. Not just iframe, you have to handle shadow DOM separately too:

https://github.com/ysmood/rod/blob/d6517f762538ac739aa27232110095e7436e708b/element_test.go#L36-L40

from chromedp.

mvdan avatar mvdan commented on May 14, 2024 3

I don't have a clear idea either. If we decide to not add new API, I think we should instead add docs and examples to explain how to deal with iframes.

from chromedp.

Ajod avatar Ajod commented on May 14, 2024 3

@jerblack Now that is some quality answer. Thank you so very much, I'll go try this out right away. Happy new year :)

from chromedp.

mvdan avatar mvdan commented on May 14, 2024 3

Apologies all for the silence here. I realise this is a problem for many people and that the passing of time can be frustrating.

We recently added a bit of new API for #463, to enable running queries on a specific node instead of always running them on the document root node.

It just occurred to me that we could use the same API for iframes. That is, using the FromNode query option with an iframe node should then run the query on that iframe's document node.

I've implemented that in the comment above, and it works. You can take a look at TestQueryIframe for a working example:

chromedp/nav_test.go

Lines 278 to 286 in 49daeb6

var iframes []*cdp.Node
if err := Run(ctx, Nodes(`iframe`, &iframes, ByQuery)); err != nil {
t.Fatal(err)
}
if err := Run(ctx,
WaitVisible(`#form`, ByID, FromNode(iframes[0])),
); err != nil {
t.Fatal(err)
}

The API for this is a bit clunky; it requires two calls to chromedp.Run, which I'm not very happy about. I'll be thinking about a nicer way to do this at a high level. Any API ideas welcome, but please keep it concise and provide a short example.

from chromedp.

ysmood avatar ysmood commented on May 14, 2024 3

@zhangguanzhang If you use rod it's like:

https://github.com/go-rod/rod/blob/e549401/examples_test.go#L67-L81

// Example_search shows how to use Search to get element inside nested iframes or shadow DOMs.
// It works the same as https://developers.google.com/web/tools/chrome-devtools/dom#search
func Example_search() {
	page := rod.New().Connect().Page("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")

	// get the code mirror editor inside the iframe and click it
	page.Search(".CodeMirror").Click()
}

How rod works is to use the ObjectID of the window element inside the iframe: https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObjectId

Here is a comparison of the examples between rod and chromedp: https://github.com/go-rod/rod/tree/master/lib/examples/compare-chromedp

from chromedp.

mvdan avatar mvdan commented on May 14, 2024 1

Ok, all queries now work with iframes; see the PR above. Please try it out and let me know how it works.

from chromedp.

KakashiHatake324 avatar KakashiHatake324 commented on May 14, 2024 1

var iframes []*cdp.Node
if err := Run(ctx, Nodes(iframe, &iframes, ByQuery)); err != nil {
t.Fatal(err)
}
if err := Run(ctx,
WaitVisible(#form, ByID, FromNode(iframes[0])),
); err != nil {
t.Fatal(err)
}

How would you use this? I am having trouble implementing this into my app. I am trying to access a cc input field and would like to send the information to the field without leaving my current page.

from chromedp.

kenshaw avatar kenshaw commented on May 14, 2024

Please stay tuned to the codebase. There are new high level actions for Inspect'ing nodes that are forthcoming. Because they are a combination of low-level protocol API calls, and Javascript, we've held off on releasing them (also because they have not been production ready yet).

from chromedp.

 avatar commented on May 14, 2024

@kenshaw maybe there is any workaround that could be used now? Thank you!

from chromedp.

yogesh-desai avatar yogesh-desai commented on May 14, 2024

@kenshaw: Thank you very much for the "chromedp". It's fantastic. I would like to know any progress on this issue. What is equivalent in chromedp, for switching to the iframe as compared to selenium.
selenium code to switch to iframe: "driver.switchTo().frame()"

from chromedp.

 avatar commented on May 14, 2024

@yogesh-desai if it's not yet implemented you always may try to use Javascript to access frames, but you would probably need to disable chrome security

from chromedp.

yogesh-desai avatar yogesh-desai commented on May 14, 2024

@CleanCodeNinja: Could you please point me out to any link to understand it in detail? I am a newbie to web-domain so please feel free to suggest any required details/ input.
Also, you can check the google-doc to understand what I want to do. Thanks

from chromedp.

powerslacker avatar powerslacker commented on May 14, 2024

Having worked with nightmare.js, I can tell you that accessing content in iframes is blocked in web browsers. Its a cross-site protection, and I've never found a way to disable it - though I haven't tried with the developer protocol.

A workaround is to get the source url of the given iframe and then navigate to it, now you have full access to the iframe's DOM.

from chromedp.

 avatar commented on May 14, 2024

@nelsonkhan navigate to iframe url would be a good approach. If you are using Chrome It's also possible to access iframes like you usually do it with "--disable-web-security" chrome command line flag.

from chromedp.

yogesh-desai avatar yogesh-desai commented on May 14, 2024

@nelsonkhan : Thank you. Yes, after googling about iframes, I also found that "same domain policy" is for security and safety.
@dreadfulangel : I have used the --disable-web-security option and was able to handle iframes.

from chromedp.

Frederic-Zhou avatar Frederic-Zhou commented on May 14, 2024

#212 (comment)

from chromedp.

integrii avatar integrii commented on May 14, 2024

I understand that we can target the iframe with an Evaluate call, but that means we can't use all the nice functions like Click() that come with chromedp. Is there some way to set the default target frame in chromedp so that the iframe becomes the "root" of all operations?

from chromedp.

integrii avatar integrii commented on May 14, 2024

That is amazing @mvdan. Thank you!

from chromedp.

kenshaw avatar kenshaw commented on May 14, 2024

An update: I don't know if it really ever will make sense to have chromedp.* actions deal with all iframes. It's all "all or nothing" aspect, and trying to make a simpler API that allows "selection" of specific iframes is going to be more cumbersome than just doing direct CDP commands/queries.

from chromedp.

kenshaw avatar kenshaw commented on May 14, 2024

@mvdan do you have any thoughts on what we should do here? I keep thinking about this, but I haven't come up with any stable API that would make any kind of sense within the context of the project.

from chromedp.

pu369 avatar pu369 commented on May 14, 2024

something like this?

self.driver.switch_to.frame(frame_reference=self.driver.find_element_by_xpath(x‌​path="//iframe[@name='editor_body']"))

https://stackoverflow.com/questions/28814916/how-do-i-select-elements-inside-an-iframe-with-xpath

from chromedp.

kenshaw avatar kenshaw commented on May 14, 2024

@pu369 -- we're open to suggestions, however what you linked to is a Selenium instance using the WebElement API, which is fundamentally different than driving Chrome raw. If I remember correctly, chromedriver did a whole lot of hacks that I'd rather avoid doing with chromedp. Please note, you can more-or-less do the above already (ie, load a iframe via a xpath query, and then switch to it), you just need to do it in two parts.

from chromedp.

haze avatar haze commented on May 14, 2024

@kenshaw How would you go by doing that? Can't find any examples

from chromedp.

pu369 avatar pu369 commented on May 14, 2024

//my ugly example to deal with iframes:

package main

import (
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"

"strings"
"time"

"github.com/chromedp/chromedp"

)

func main() {
Testiframe()

}
func Testiframe() {
//static file server
wd, _ := os.Getwd()
http.Handle("/", http.FileServer(http.Dir(wd)))
http.Handle("/index", writeHTML(`<!doctype html>

the content
<iframe src="123.html"></iframe> `)) go http.ListenAndServe(":9090", nil) framefile123 := ` clickme! ` f123 := []byte(framefile123) if err := ioutil.WriteFile("./123.html", f123, 0666); err != nil { panic(err) }
//show chrome
options := []chromedp.ExecAllocatorOption{
	chromedp.Flag("headless", false),
	chromedp.Flag("hide-scrollbars", false),
	chromedp.Flag("mute-audio", false),
	chromedp.UserAgent(`Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36`),
}
options = append(chromedp.DefaultExecAllocatorOptions[:], options...)

allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), options...)
defer cancel()
ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
var outer, url, frameurl string
var ok bool
if err := chromedp.Run(ctx,
	chromedp.Navigate("http://localhost:9090/index"),
	chromedp.Sleep(2*time.Second),
	chromedp.WaitVisible("iframe", chromedp.ByQuery),
	chromedp.AttributeValue("iframe", "src", &outer, &ok),
	chromedp.Location(&url),
); err != nil {
	panic(err)
}
fmt.Println("Outer:", outer, " url:", url)
frameurl = url[0:strings.LastIndex(url, "/")] + "/" + outer
fmt.Println("frameurl", frameurl)
if err := chromedp.Run(ctx,
	chromedp.Navigate(frameurl),
	chromedp.Sleep(2*time.Second),
	chromedp.WaitVisible("#clickme"),
	chromedp.Click("#clickme", chromedp.ByID),
	chromedp.Sleep(2*time.Second),
); err != nil {
	panic(err)
}

}
func writeHTML(content string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//utf-8
w.Header().Set("Content-Type", "text/html; charset=utf-8")
io.WriteString(w, strings.TrimSpace(content))
})
}

from chromedp.

Ajod avatar Ajod commented on May 14, 2024

@pu369 -- we're open to suggestions, however what you linked to is a Selenium instance using the WebElement API, which is fundamentally different than driving Chrome raw. If I remember correctly, chromedriver did a whole lot of hacks that I'd rather avoid doing with chromedp. Please note, you can more-or-less do the above already (ie, load a iframe via a xpath query, and then switch to it), you just need to do it in two parts.

Hi,

I'm interested in this. How do you indeed go about doing the "switch to it" part ? Loading an iframe via an xpath query is simple enough, but I'm getting confused as to how one would switch to it, creating a new child context and such.

Edit for clarity: previous answer navigates to the frame while parsing the url attribute. If I simply want to, say, click the first input element inside an iframe in the page, how would I make my current context the iframe's before I can click and simply jump back to parent context ?

from chromedp.

alufrew avatar alufrew commented on May 14, 2024

So the only way to do this is disable --disable-web-security and use evaluate? In my case iFrame is not loaded when disabling web security and it's not on the target list.
So is there some way to make new target from Frame ID for example?

from chromedp.

mvdan avatar mvdan commented on May 14, 2024

Err, I made a mistake in that code above; WaitVisible doesn't seem to work, but WaitReady does work. I'll investigate that separately. In any case, the node selectors work, which was the main blocker.

from chromedp.

ydcool avatar ydcool commented on May 14, 2024

Apologies all for the silence here. I realise this is a problem for many people and that the passing of time can be frustrating.

We recently added a bit of new API for #463, to enable running queries on a specific node instead of always running them on the document root node.

It just occurred to me that we could use the same API for iframes. That is, using the FromNode query option with an iframe node should then run the query on that iframe's document node.

I've implemented that in the comment above, and it works. You can take a look at TestQueryIframe for a working example:

chromedp/nav_test.go

Lines 278 to 286 in 49daeb6

var iframes []*cdp.Node
if err := Run(ctx, Nodes(`iframe`, &iframes, ByQuery)); err != nil {
t.Fatal(err)
}
if err := Run(ctx,
WaitVisible(`#form`, ByID, FromNode(iframes[0])),
); err != nil {
t.Fatal(err)
}

The API for this is a bit clunky; it requires two calls to chromedp.Run, which I'm not very happy about. I'll be thinking about a nicer way to do this at a high level. Any API ideas welcome, but please keep it concise and provide a short example.

This is an great update, but some functions still not work with FromNode ,such as Click, right?

from chromedp.

mvdan avatar mvdan commented on May 14, 2024

Yes, the basics work, but some of the extras like Click and WaitVisible do not - I assume some extra code to handle iframe coordinates is needed.

from chromedp.

zhangguanzhang avatar zhangguanzhang commented on May 14, 2024

in some iframe, None of the following methods worked, but I typed a JS expression in the browser's Console and it worked

chromedp.EvaluateAsDevTools
chromedp.Evaluate
runtime.Evaluate

from chromedp.

Lua12138 avatar Lua12138 commented on May 14, 2024

Now, what should I do? I can execute JavaScript in a specified context (for example, in an iframe).

from chromedp.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.