How does stackoverflow set origin: null in http requests [duplicate] - javascript

As you can see from this Bugzilla thread (and also), Firefox does not always send an Origin header in POST requests. The RFC states that it should not be sent in certain undefined "privacy-sensitive" contexts. Mozilla defines those contexts here.
I'd like to know whether these are the only situations in which Firefox will not send the Origin header. As far as I can tell, it also will not send it in cross-origin POST requests (though Chrome and Internet Explorer will), but I can't confirm that in the documentation. Is it enumerated somewhere that I'm missing?

As far as what the relevant specs actually require, the answer has a couple parts:
When browsers must internally set an origin to a value that’ll get serialized as null
When browsers must send the Origin header
Here are the details:
When browsers must set origin to a value that’ll get serialized as null
The HTML spec uses the term opaque origin and defines it as an “internal value”:
with no serialization it can be recreated from (it is serialized as "null" per ASCII serialization of an origin), for which the only meaningful operation is testing for equality
In other words everywhere the HTML spec says opaque origin, you can translate that to null.
The HTML spec requires browsers to set an opaque origin or unique origin in these cases:
Cross-origin images (including cross-origin img elements)
Cross-origin media data (including cross-origin video and audio elements)
Any document generated from a data: URL
Any iframe with a sandbox attribute that doesn’t contain the value allow-same-origin
Any document programmatically created using createDocument(), etc.
Any document that does not have a creator browsing context
Responses that are network errors
The Should navigation response to navigation request of type from source in target be blocked by Content Security Policy? algorithm returns Blocked when executed on a navigate response
The Fetch spec requires browsers to set the origin to a “globally unique identifier” (which basically means the same thing as “opaque origin” which basically means null…) in one case:
Redirects across origins
The URL spec requires browsers to set an opaque origin in the following cases:
For blob: URLs
For file: URLs
For any other URLs whose scheme is not one of http, https, ftp, ws, wss, or gopher.
But note that just because the browser has internally set an opaque origin—essentially null—that doesn’t necessarily mean the browser will send an Origin header. So see the next part of this answer for details about when browsers must send the Origin header.
When browsers must send the Origin header
Browsers send the Origin header for cross-origin requests initiated by a fetch() or XHR call, or by an ajax method from a JavaScript library (axios, jQuery, etc.) — but not for normal page navigations (that is, when you open a web page directly in a browser), and not (normally) for resources embedded in a web page (for example, not for CSS stylesheets, scripts, or images).
But that description is a simplification. There are cases other than cross-origin XHR/fetch/ajax calls when browsers send the Origin header, and cases when browsers send the Origin header for embedded resources. So what follows below is the longer answer.
In terms of the spec requirements: The spec requires the Origin header to be sent only for any request which the Fetch spec defines as a CORS request:
A CORS request is an HTTP request that includes an Origin header. It cannot be reliably identified as participating in the CORS protocol as the Origin header is also included for all requests whose method is neither GET nor HEAD.
So, what the spec means there is: The Origin header is sent in all cross-origin requests, but it’s also always sent for all POST, PUT, PATCH, and DELETE requests — even for same-origin POST, PUT, PATCH, and DELETE requests (which by definition in Fetch are actually “CORS requests” — even though they’re same-origin).*
The other cases when browsers must send the Origin header are any cases where a request is made with the “CORS flag” set — which, as far as HTTP(S) requests, is except when the request mode is navigate, websocket, same-origin, or no-cors.
XHR always sets the mode to cors. But with the Fetch API, those request modes are the ones you can set with the mode field of the init-object argument to the fetch(…) method:
fetch("http://example.com", { mode: 'no-cors' }) // no Origin will be sent
Font requests always have the mode set to cors and so always have the Origin header.
And for any element with a crossorigin attribute (aka “CORS setting attribute”), the HTML spec requires browsers to set the request mode to cors (and to send the Origin header).
Otherwise, for embedded resources — any elements having attributes with URLs that initiate requests (<script src>, stylesheets, images, media elements) — the mode for the requests defaults to no-cors; and since those requests are GET requests, that means, per-spec, browsers send no Origin header for them.
When HTML form elements initiate POST requests, the mode for those POSTs also defaults to no-cors — in the same way that embedded resources have their mode defaulted to no-cors. However, unlike the no-cors mode GET requests for embedded resources, browsers do send the Origin header for those no-cors mode POSTs initiated from HTML form elements.
The reason for that is, as mentioned earlier in this answer, browsers always send the Origin header in all POST, PUT, PATCH, and DELETE requests.
Also, for completeness here and to be clear: For navigations, browsers send no Origin header. That is, if a user navigates directly to a resource — by pasting a URL into a browser address bar, or by following a link from another web document — then browsers send no Origin header.
* The algorithm in the Fetch spec that requires browsers to send the Origin header for all CORS requests is this:
To append a request Origin header, given a request request, run these steps:
1. Let serializedOrigin be the result of byte-serializing a request origin with request.
2. If request’s response tainting is "cors" or request’s mode is "websocket", then
    append Origin/serializedOrigin to request’s header list.
3. Otherwise, if request’s method is neither GET nor HEAD,
    then: [also send the Origin header in that case too]
Step 2 there is what requires the Origin header to be sent in all cross-origin requests — because all cross-origin requests have their response tainting set to "cors".
But step 3 there requires the Origin header to also be sent for same-origin POST, PUT, PATCH, and DELETE requests (which by definition in Fetch are actually “CORS requests” — even though they’re same-origin).
The above describes how the Fetch spec currently defines the requirements, due to a change that was made to the spec on 2016-12-09. Up until then the requirements were different:
  •  previously no Origin was sent for a same-origin POST
  •  previously no Origin was sent for cross-origin POST from a <form> (without CORS)
So the Firefox behavior the question describes is what the spec previously required, not what it currently requires.

For me this was happening on a super-standard form POST to a relative URL on localhost, and seems to have been triggered by having
<meta name="referrer" content="no-referrer">
in the <head>.
Changing it to
<meta name="referrer" content="same-origin">
seemed to make Firefox happier.

Related

Can't get basic HTTP POST function to work from localhost with Javascript [duplicate]

I am building a web API. I found whenever I use Chrome to POST, GET to my API, there is always an OPTIONS request sent before the real request, which is quite annoying. Currently, I get the server to ignore any OPTIONS requests. Now my question is what's good to send an OPTIONS request to double the server's load? Is there any way to completely stop the browser from sending OPTIONS requests?
edit 2018-09-13: added some precisions about this pre-flight request and how to avoid it at the end of this reponse.
OPTIONS requests are what we call pre-flight requests in Cross-origin resource sharing (CORS).
They are necessary when you're making requests across different origins in specific situations.
This pre-flight request is made by some browsers as a safety measure to ensure that the request being done is trusted by the server.
Meaning the server understands that the method, origin and headers being sent on the request are safe to act upon.
Your server should not ignore but handle these requests whenever you're attempting to do cross origin requests.
A good resource can be found here http://enable-cors.org/
A way to handle these to get comfortable is to ensure that for any path with OPTIONS method the server sends a response with this header
Access-Control-Allow-Origin: *
This will tell the browser that the server is willing to answer requests from any origin.
For more information on how to add CORS support to your server see the following flowchart
http://www.html5rocks.com/static/images/cors_server_flowchart.png
edit 2018-09-13
CORS OPTIONS request is triggered only in somes cases, as explained in MDN docs:
Some requests don’t trigger a CORS preflight. Those are called “simple requests” in this article, though the Fetch spec (which defines CORS) doesn’t use that term. A request that doesn’t trigger a CORS preflight—a so-called “simple request”—is one that meets all the following conditions:
The only allowed methods are:
GET
HEAD
POST
Apart from the headers set automatically by the user agent (for example, Connection, User-Agent, or any of the other headers with names defined in the Fetch spec as a “forbidden header name”), the only headers which are allowed to be manually set are those which the Fetch spec defines as being a “CORS-safelisted request-header”, which are:
Accept
Accept-Language
Content-Language
Content-Type (but note the additional requirements below)
DPR
Downlink
Save-Data
Viewport-Width
Width
The only allowed values for the Content-Type header are:
application/x-www-form-urlencoded
multipart/form-data
text/plain
No event listeners are registered on any XMLHttpRequestUpload object used in the request; these are accessed using the XMLHttpRequest.upload property.
No ReadableStream object is used in the request.
Have gone through this issue, below is my conclusion to this issue and my solution.
According to the CORS strategy (highly recommend you read about it) You can't just force the browser to stop sending OPTIONS request if it thinks it needs to.
There are two ways you can work around it:
Make sure your request is a "simple request"
Set Access-Control-Max-Age for the OPTIONS request
Simple request
A simple cross-site request is one that meets all the following conditions:
The only allowed methods are:
GET
HEAD
POST
Apart from the headers set automatically by the user agent (e.g. Connection, User-Agent, etc.), the only headers which are allowed to be manually set are:
Accept
Accept-Language
Content-Language
Content-Type
The only allowed values for the Content-Type header are:
application/x-www-form-urlencoded
multipart/form-data
text/plain
A simple request will not cause a pre-flight OPTIONS request.
Set a cache for the OPTIONS check
You can set a Access-Control-Max-Age for the OPTIONS request, so that it will not check the permission again until it is expired.
Access-Control-Max-Age gives the value in seconds for how long the response to the preflight request can be cached for without sending another preflight request.
Limitation Noted
For Chrome, the maximum seconds for Access-Control-Max-Age is 600 which is 10 minutes, according to chrome source code
Access-Control-Max-Age only works for one resource every time, for example, GET requests with same URL path but different queries will be treated as different resources. So the request to the second resource will still trigger a preflight request.
Please refer this answer on the actual need for pre-flighted OPTIONS request: CORS - What is the motivation behind introducing preflight requests?
To disable the OPTIONS request, below conditions must be satisfied for ajax request:
Request does not set custom HTTP headers like 'application/xml' or 'application/json' etc
The request method has to be one of GET, HEAD or POST. If POST, content type should be one of application/x-www-form-urlencoded, multipart/form-data, or text/plain
Reference:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
When you have the debug console open and the Disable Cache option turned on, preflight requests will always be sent (i.e. before each and every request). if you don't disable the cache, a pre-flight request will be sent only once (per server)
Yes it's possible to avoid options request. Options request is a preflight request when you send (post) any data to another domain. It's a browser security issue. But we can use another technology: iframe transport layer. I strongly recommend you forget about any CORS configuration and use readymade solution and it will work anywhere.
Take a look here:
https://github.com/jpillora/xdomain
And working example:
http://jpillora.com/xdomain/
For a developer who understands the reason it exists but needs to access an API that doesn't handle OPTIONS calls without auth, I need a temporary answer so I can develop locally until the API owner adds proper SPA CORS support or I get a proxy API up and running.
I found you can disable CORS in Safari and Chrome on a Mac.
Disable same origin policy in Chrome
Chrome: Quit Chrome, open an terminal and paste this command: open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir
Safari: Disabling same-origin policy in Safari
If you want to disable the same-origin policy on Safari (I have 9.1.1), then you only need to enable the developer menu, and select "Disable Cross-Origin Restrictions" from the develop menu.
As mentioned in previous posts already, OPTIONS requests are there for a reason. If you have an issue with large response times from your server (e.g. overseas connection) you can also have your browser cache the preflight requests.
Have your server reply with the Access-Control-Max-Age header and for requests that go to the same endpoint the preflight request will have been cached and not occur anymore.
I have solved this problem like.
if($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && ENV == 'devel') {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: X-Requested-With');
header("HTTP/1.1 200 OK");
die();
}
It is only for development. With this I am waiting 9ms and 500ms and not 8s and 500ms. I can do that because production JS app will be on the same machine as production so there will be no OPTIONS but development is my local.
You can't but you could avoid CORS using JSONP.
After spending a whole day and a half trying to work through a similar problem I found it had to do with IIS.
My Web API project was set up as follows:
// WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
//...
}
I did not have CORS specific config options in the web.config > system.webServer node like I have seen in so many posts
No CORS specific code in the global.asax or in the controller as a decorator
The problem was the app pool settings.
The managed pipeline mode was set to classic (changed it to integrated) and the Identity was set to Network Service (changed it to ApplicationPoolIdentity)
Changing those settings (and refreshing the app pool) fixed it for me.
OPTIONS request is a feature of web browsers, so it's not easy to disable it. But I found a way to redirect it away with proxy. It's useful in case that the service endpoint just cannot handle CORS/OPTIONS yet, maybe still under development, or mal-configured.
Steps:
Setup a reverse proxy for such requests with tools of choice (nginx, YARP, ...)
Create an endpoint just to handle the OPTIONS request. It might be easier to create a normal empty endpoint, and make sure it handles CORS well.
Configure two sets of rules for the proxy. One is to route all OPTIONS requests to the dummy endpoint above. Another to route all other requests to actual endpoint in question.
Update the web site to use proxy instead.
Basically this approach is to cheat browser that OPTIONS request works. Considering CORS is not to enhance security, but to relax the same-origin policy, I hope this trick could work for a while. :)
you can also use a API Manager (like Open Sources Gravitee.io) to prevent CORS issues between frontend app and backend services by manipulating headers in preflight.
Header used in response to a preflight request to indicate which HTTP headers can be used when making the actual request :
content-type
access-control-allow-header
authorization
x-requested-with
and specify the "allow-origin" = localhost:4200 for example
One solution I have used in the past - lets say your site is on mydomain.com, and you need to make an ajax request to foreigndomain.com
Configure an IIS rewrite from your domain to the foreign domain - e.g.
<rewrite>
<rules>
<rule name="ForeignRewrite" stopProcessing="true">
<match url="^api/v1/(.*)$" />
<action type="Rewrite" url="https://foreigndomain.com/{R:1}" />
</rule>
</rules>
</rewrite>
on your mydomain.com site - you can then make a same origin request, and there's no need for any options request :)
It can be solved in case of use of a proxy that intercept the request and write the appropriate headers.
In the particular case of Varnish these would be the rules:
if (req.http.host == "CUSTOM_URL" ) {
set resp.http.Access-Control-Allow-Origin = "*";
if (req.method == "OPTIONS") {
set resp.http.Access-Control-Max-Age = "1728000";
set resp.http.Access-Control-Allow-Methods = "GET, POST, PUT, DELETE, PATCH, OPTIONS";
set resp.http.Access-Control-Allow-Headers = "Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since";
set resp.http.Content-Length = "0";
set resp.http.Content-Type = "text/plain charset=UTF-8";
set resp.status = 204;
}
}
What worked for me was to import "github.com/gorilla/handlers" and then use it this way:
router := mux.NewRouter()
router.HandleFunc("/config", getConfig).Methods("GET")
router.HandleFunc("/config/emcServer", createEmcServers).Methods("POST")
headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type"})
originsOk := handlers.AllowedOrigins([]string{"*"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})
log.Fatal(http.ListenAndServe(":" + webServicePort, handlers.CORS(originsOk, headersOk, methodsOk)(router)))
As soon as I executed an Ajax POST request and attaching JSON data to it, Chrome would always add the Content-Type header which was not in my previous AllowedHeaders config.

CORS redirect works unexpectedly

According to CORS specification (7.1.7 - Redirect Steps (for Simple Cross-Origin Request)):
If the request URL origin is not same origin with the original URL origin, set source origin to a globally unique identifier (becomes "null" when transmitted).
I have a scenario where javascript from a.blah.com makes a CORS request (i.e. Origin request header present) by sending browser to b.blah.com, which responds with a 302 and location = c.blah.com. If I am reading the spec correctly, this should result in the request to c.blah.com containing Origin header = "null". Instead, the Origin header is not present and thus the request to c.blah.com is not considered a CORS request.
The above behavior was experienced in Chrome 54. I have not confirmed the exact request contents in other browsers, but I have checked that my particular application flow works in Chrome 54, Firefox 37, and IE 11 browsers, which implies they never see Origin header set to "null" (my services will fail requests loudly if the receive an Origin = "null").
This all worries me because while my application is working, it actually shouldn't be, and I don't want to just ignore this fact. Am I misunderstanding the spec? Are there any caveats to the spec behavior that I've missed?
All traffic is HTTPS, not returning * (wildcard) in CORS response header, setting with-credentials flags/headers as appropriate, no proxies in use, all actors on separate machines so should not be a localhost gotcha...
Thanks.
In my original configuration, the request to b.blah.com was a form posted by js (not xhr). After some digging around, it seems that since the request was triggered by js, that warranted an Origin header on the request to b.blah.com, but the resulting redirect to c.blah.com was handled by the browser without any script/xhr intervention, so the redirect was not decorated with an Origin header.
I set up a test where the request to b.blah.com was xhr, and that did cause an Origin = "null" on the redirect to c.blah.com.
I suppose I need to better research the nuances of when same-origin policy is enforced.
Thanks.

Why does the browser allow xorigin POST but not PUT?

Consider the very simple example of using XMLHttpRequest.
The following posts properly ( you can see it in the network tab or by directing your browser to http://requestb.in/yckncpyc) although it prints a warning to the console
XMLHttpRequest cannot load http://requestb.in/yckncpyc. No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'null' is therefore not allowed access.
const method = "POST"
const req = new XMLHttpRequest()
req.open(method, 'http://requestb.in/yckncpyc')
req.send("foobar")
console.log("sent")
req.addEventListener('load', function() { console.log(req.status, req.response) })
Sure. I get that. What I don't get is why merely changing the verb used to a PUT results in something completely different. The request sent is an OPTIONS preflight request and prints
XMLHttpRequest cannot load http://requestb.in/yckncpyc. Response to
preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'null' is therefore not allowed access.
const method = "PUT"
const req = new XMLHttpRequest()
req.open(method, 'http://requestb.in/yckncpyc')
req.send("foobar")
console.log("sent")
req.addEventListener('load', function() { console.log(req.status, req.response) })
Why does the browser* treat these differently? It seems like something that would be done for security but that really makes no sense since an attacker can always use a POST instead of a PUT.
So what is the logic here?
Tried this in Chrome 52, Safari 9.1.2
GET, HEAD, and POST requests (with a couple other restrictions) can be made cross-origin with no additional communication. The responses cannot be examined, but the requests are allowed.
Anything else requires a preflight request to check the headers from the target site to see whether the request would be allowed.
The reason for such a setup is that GET, HEAD, and POST were historically allowed from browsers as a natural part of HTML semantics. Tags for scripts and CSS and images do GET requests, and forms do POSTs. When the CORS stuff was introduced, therefore, those were allowed under the assumption that sites were no more vulnerable to simple requests like that in an XHR world then they were in the simpler non-XHR world.
So simple requests are allowed, and the browser looks at the response headers to decide whether the requesting code in the cross-origin page should be allowed to see the response content. For other requests, the browser first sends an OPTIONS request to check the CORS response headers. Only if that looks OK (that is, if the response headers contain the appropriate "yes that's OK" headers) will the XHR be allowed to proceed.

how to use Service Worker to cache cross domain resources if the response is 404?

w3:
6.2 Cross-Origin Resources and CORS¶
Applications tend to cache items that come from a CDN or other origin.
It is possible to request many of them directly using <script>, <img>, <video>
and <link> elements. It would be hugely limiting if this sort of runtime
collaboration broke when offline.
Similarly, it is possible to XHR many sorts of off-origin resources when
appropriate CORS headers are set.
ServiceWorkers enable this by allowing Caches to fetch and cache off-origin
items. Some restrictions apply, however. First, unlike same-origin resources
which are managed in the Cache as Response objects with the type attribute set
to "basic", the objects stored are Response objects with the type attribute set
to "opaque". Responses typed "opaque" provide a much less expressive API than
Responses typed "basic"; the bodies and headers cannot be read or set, nor many
of the other aspects of their content inspected. They can be passed to
event.respondWith(r) method in the same manner as the Responses typed "basic",
but cannot be meaningfully created programmatically. These limitations are
necessary to preserve the security invariants of the platform. Allowing Caches
to store them allows applications to avoid re-architecting in most cases.
I have set the CORS header like:
Access-Control-Allow-Origin:https://xxx.xx.x.com
Access-Control-Allow-Credentials:true
but I still get an "opaque" response and I cannot ensure the code is 200.
If I cache these unsuccessful responses, it will cause some problem.
For example, a chum of network causes a 404 to the cross domain resources,
and I cache it, then I will always use this 404 cache response even thongth when
the network problem is corrected. The same-origin resource do not have this problem.
The mode of a Request (allegedly) defaults to "no-cors". (I say "allegedly" because I believe I've seen situations in which an implicitly created Request used in fetch() results in a CORS-enabled Response.)
So you should be explicit about opting in to CORS if you know that your server supports it:
var corsRequest = new Request(url, {mode: 'cors'});
fetch(corsRequest).then(response => ...); // response won't be opaque.
Given a properly configured remote server, a CORS-enabled Request will result in a Response that has a type of "cors". Unlike an "opaque" Response, a "cors" Response will expose the underlying status, body, etc.
Unfortunately, there's no way to detect it.
For security reasons, it's explicitly not allowed: https://github.com/whatwg/fetch/issues/14.

error : Permission denied to access property 'document'

How can I fix this message in Firefox? I am using an Iframe which has an anchor tag? I would like to get a reference to this anchor but i am getting this error when I am trying to access anchor:
var frameWindow = document.getElementById('myIframe').contentWindow;
var anchor = frameWindow.document.links[0]; //.getElementsByClassName('a');
anchor.onclick....
Relaxing the same-origin policy
In some circumstances the same-origin policy is too restrictive, posing problems for large websites that use multiple subdomains. Here are four techniques for relaxing it:
document.domain property
If two windows (or frames) contain scripts that set domain to the same value, the same-origin policy is relaxed for these two windows, and each window can interact with the other. For example, cooperating scripts in documents loaded from orders.example.com and catalog.example.com might set their document.domain properties to “example.com”, thereby making the documents appear to have the same origin and enabling each document to read properties of the other. This might not always work as the port stored in the internal representation can become marked as null. In other words example.com port 80 will become example.com port null because we update document.domain. Port null might not be treated as 80 ( depending on your browser ) and hence might fail or succeed depending on your browser.
Cross-Origin Resource Sharing
The second technique for relaxing the same-origin policy is being standardized under the name Cross-Origin Resource Sharing. This draft standard extends HTTP with a new Origin request header and a new Access-Control-Allow-Origin response header. It allows servers to use a header to explicitly list origins that may request a file or to use a wildcard and allow a file to be requested by any site. Browsers such as Firefox 3.5 and Safari 4 use this new header to allow the cross-origin HTTP requests with XMLHttpRequest that would otherwise have been forbidden by the same-origin policy.[7]
Cross-document messaging
Another new technique, cross-document messaging allows a script from one page to pass textual messages to a script on another page regardless of the script origins. Calling the postMessage() method on a Window object asynchronously fires an "onmessage" event in that window, triggering any user-defined event handlers. A script in one page still cannot directly access methods or variables in the other page, but they can communicate safely through this message-passing technique.
JSONP
JSONP allows a page to receive JSON data from a different domain by adding a <script> element to the page which loads a JSON response from a different domain.
The function call is the "P" of JSONP—the "padding" around the pure JSON, or according to some the "prefix". By convention, the browser provides the name of the callback function as a named query parameter value, typically using the name jsonp or callback as the named query parameter field name, in its request to the server, e.g.,
<script type="application/javascript"
src="http://server2.example.com/Users/1234?jsonp=parseResponse">
</script>
In this example, the received payload would be:
parseResponse({"Name": "Foo", "Id": 1234, "Rank": 7});
If the iframe points to a different domain, you will get this error. This is an example of your browser preventing cross-site scripting: http://en.wikipedia.org/wiki/Cross-site_scripting
The error is due to the same-origin policy like explained in other answers. I post this answer as a workaround to execute JavaScript code in a web console.
My comment suggesting to use Firebug CD command no longer works because Firebug is not supported anymore.
But there is a similar feature in Firefox Developer Tools, you can switch the domain name by selecting the iframe context picker button like described here: https://developer.mozilla.org/en-US/docs/Tools/Working_with_iframes

Categories

Resources