1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/LocationBase.h"
8 #include "nsIScriptSecurityManager.h"
9 #include "nsIScriptContext.h"
10 #include "nsDocShellLoadState.h"
11 #include "nsIWebNavigation.h"
12 #include "nsNetUtil.h"
15 #include "nsContentUtils.h"
16 #include "nsGlobalWindowInner.h"
17 #include "mozilla/NullPrincipal.h"
18 #include "mozilla/dom/Document.h"
19 #include "mozilla/dom/ReferrerInfo.h"
20 #include "mozilla/dom/WindowContext.h"
22 namespace mozilla::dom
{
24 already_AddRefed
<nsDocShellLoadState
> LocationBase::CheckURL(
25 nsIURI
* aURI
, nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aRv
) {
26 RefPtr
<BrowsingContext
> bc(GetBrowsingContext());
27 if (NS_WARN_IF(!bc
)) {
28 aRv
.Throw(NS_ERROR_NOT_AVAILABLE
);
32 nsCOMPtr
<nsIPrincipal
> triggeringPrincipal
;
33 nsCOMPtr
<nsIURI
> sourceURI
;
34 ReferrerPolicy referrerPolicy
= ReferrerPolicy::_empty
;
35 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
37 // Get security manager.
38 nsIScriptSecurityManager
* ssm
= nsContentUtils::GetSecurityManager();
39 if (NS_WARN_IF(!ssm
)) {
40 aRv
.Throw(NS_ERROR_UNEXPECTED
);
44 // Check to see if URI is allowed. We're not going to worry about a
45 // window ID here because it's not 100% clear which window's id we
46 // would want, and we're throwing a content-visible exception
48 nsresult rv
= ssm
->CheckLoadURIWithPrincipal(
49 &aSubjectPrincipal
, aURI
, nsIScriptSecurityManager::STANDARD
, 0);
50 if (NS_WARN_IF(NS_FAILED(rv
))) {
53 aRv
.ThrowTypeError
<MSG_URL_NOT_LOADABLE
>(spec
);
57 // Make the load's referrer reflect changes to the document's URI caused by
58 // push/replaceState, if possible. First, get the document corresponding to
59 // fp. If the document's original URI (i.e. its URI before
60 // push/replaceState) matches the principal's URI, use the document's
61 // current URI as the referrer. If they don't match, use the principal's
64 // The triggering principal for this load should be the principal of the
65 // incumbent document (which matches where the referrer information is
66 // coming from) when there is an incumbent document, and the subject
67 // principal otherwise. Note that the URI in the triggering principal
68 // may not match the referrer URI in various cases, notably including
69 // the cases when the incumbent document's document URI was modified
70 // after the document was loaded.
72 nsCOMPtr
<nsPIDOMWindowInner
> incumbent
=
73 do_QueryInterface(mozilla::dom::GetIncumbentGlobal());
74 nsCOMPtr
<Document
> doc
= incumbent
? incumbent
->GetDoc() : nullptr;
77 RefPtr
<nsDocShellLoadState
> loadState
= new nsDocShellLoadState(aURI
);
80 // No document; just use our subject principal as the triggering principal.
81 loadState
->SetTriggeringPrincipal(&aSubjectPrincipal
);
82 return loadState
.forget();
85 nsCOMPtr
<nsIURI
> docOriginalURI
, docCurrentURI
, principalURI
;
86 docOriginalURI
= doc
->GetOriginalURI();
87 docCurrentURI
= doc
->GetDocumentURI();
88 nsCOMPtr
<nsIPrincipal
> principal
= doc
->NodePrincipal();
90 triggeringPrincipal
= doc
->NodePrincipal();
91 referrerPolicy
= doc
->GetReferrerPolicy();
93 bool urisEqual
= false;
94 if (docOriginalURI
&& docCurrentURI
&& principal
) {
95 principal
->EqualsURI(docOriginalURI
, &urisEqual
);
98 referrerInfo
= new ReferrerInfo(docCurrentURI
, referrerPolicy
);
100 principal
->CreateReferrerInfo(referrerPolicy
, getter_AddRefs(referrerInfo
));
102 loadState
->SetTriggeringPrincipal(triggeringPrincipal
);
103 loadState
->SetTriggeringSandboxFlags(doc
->GetSandboxFlags());
104 loadState
->SetCsp(doc
->GetCsp());
106 loadState
->SetReferrerInfo(referrerInfo
);
108 loadState
->SetHasValidUserGestureActivation(
109 doc
->HasValidTransientUserGestureActivation());
111 return loadState
.forget();
114 void LocationBase::SetURI(nsIURI
* aURI
, nsIPrincipal
& aSubjectPrincipal
,
115 ErrorResult
& aRv
, bool aReplace
) {
116 RefPtr
<BrowsingContext
> bc
= GetBrowsingContext();
117 if (!bc
|| bc
->IsDiscarded()) {
121 CallerType callerType
= aSubjectPrincipal
.IsSystemPrincipal()
123 : CallerType::NonSystem
;
125 nsresult rv
= bc
->CheckLocationChangeRateLimit(callerType
);
131 RefPtr
<nsDocShellLoadState
> loadState
=
132 CheckURL(aURI
, aSubjectPrincipal
, aRv
);
138 loadState
->SetLoadType(LOAD_STOP_CONTENT_AND_REPLACE
);
140 loadState
->SetLoadType(LOAD_STOP_CONTENT
);
143 // Get the incumbent script's browsing context to set as source.
144 nsCOMPtr
<nsPIDOMWindowInner
> sourceWindow
=
145 nsContentUtils::IncumbentInnerWindow();
147 WindowContext
* context
= sourceWindow
->GetWindowContext();
148 loadState
->SetSourceBrowsingContext(sourceWindow
->GetBrowsingContext());
149 loadState
->SetHasValidUserGestureActivation(
150 context
&& context
->HasValidTransientUserGestureActivation());
153 loadState
->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE
);
154 loadState
->SetFirstParty(true);
156 rv
= bc
->LoadURI(loadState
);
157 if (NS_WARN_IF(NS_FAILED(rv
))) {
158 if (rv
== NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI
&&
159 net::SchemeIsJavascript(loadState
->URI())) {
160 // Per spec[1], attempting to load a javascript: URI into a cross-origin
161 // BrowsingContext is a no-op, and should not raise an exception.
162 // Technically, Location setters run with exceptions enabled should only
163 // throw an exception[2] when the caller is not allowed to navigate[3] the
164 // target browsing context due to sandboxing flags or not being
165 // closely-related enough, though in practice we currently throw for other
169 // https://html.spec.whatwg.org/multipage/browsing-the-web.html#javascript-protocol
171 // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate
173 // https://html.spec.whatwg.org/multipage/browsers.html#allowed-to-navigate
180 Document
* doc
= bc
->GetDocument();
181 if (doc
&& nsContentUtils::IsExternalProtocol(aURI
)) {
182 doc
->EnsureNotEnteringAndExitFullscreen();
186 void LocationBase::SetHref(const nsAString
& aHref
,
187 nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aRv
) {
188 DoSetHref(aHref
, aSubjectPrincipal
, false, aRv
);
191 void LocationBase::DoSetHref(const nsAString
& aHref
,
192 nsIPrincipal
& aSubjectPrincipal
, bool aReplace
,
194 // Get the source of the caller
195 nsCOMPtr
<nsIURI
> base
= GetSourceBaseURL();
196 SetHrefWithBase(aHref
, base
, aSubjectPrincipal
, aReplace
, aRv
);
199 void LocationBase::SetHrefWithBase(const nsAString
& aHref
, nsIURI
* aBase
,
200 nsIPrincipal
& aSubjectPrincipal
,
201 bool aReplace
, ErrorResult
& aRv
) {
203 nsCOMPtr
<nsIURI
> newUri
;
205 if (Document
* doc
= GetEntryDocument()) {
206 result
= NS_NewURI(getter_AddRefs(newUri
), aHref
,
207 doc
->GetDocumentCharacterSet(), aBase
);
209 result
= NS_NewURI(getter_AddRefs(newUri
), aHref
, nullptr, aBase
);
212 if (NS_FAILED(result
) || !newUri
) {
213 aRv
.ThrowSyntaxError("'"_ns
+ NS_ConvertUTF16toUTF8(aHref
) +
214 "' is not a valid URL."_ns
);
218 /* Check with the scriptContext if it is currently processing a script tag.
219 * If so, this must be a <script> tag with a location.href in it.
220 * we want to do a replace load, in such a situation.
221 * In other cases, for example if a event handler or a JS timer
222 * had a location.href in it, we want to do a normal load,
223 * so that the new url will be appended to Session History.
224 * This solution is tricky. Hopefully it isn't going to bite
225 * anywhere else. This is part of solution for bug # 39938, 72197
227 bool inScriptTag
= false;
228 nsIScriptContext
* scriptContext
= nullptr;
229 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(GetEntryGlobal());
231 scriptContext
= nsGlobalWindowInner::Cast(win
)->GetContextInternal();
235 if (scriptContext
->GetProcessingScriptTag()) {
236 // Now check to make sure that the script is running in our window,
237 // since we only want to replace if the location is set by a
238 // <script> tag in the same window. See bug 178729.
239 nsCOMPtr
<nsIDocShell
> docShell(GetDocShell());
240 nsCOMPtr
<nsIScriptGlobalObject
> ourGlobal
=
241 docShell
? docShell
->GetScriptGlobalObject() : nullptr;
242 inScriptTag
= (ourGlobal
== scriptContext
->GetGlobalObject());
246 SetURI(newUri
, aSubjectPrincipal
, aRv
, aReplace
|| inScriptTag
);
249 void LocationBase::Replace(const nsAString
& aUrl
,
250 nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aRv
) {
251 DoSetHref(aUrl
, aSubjectPrincipal
, true, aRv
);
254 nsIURI
* LocationBase::GetSourceBaseURL() {
255 Document
* doc
= GetEntryDocument();
257 // If there's no entry document, we either have no Script Entry Point or one
258 // that isn't a DOM Window. This doesn't generally happen with the DOM, but
259 // can sometimes happen with extension code in certain IPC configurations. If
260 // this happens, try falling back on the current document associated with the
261 // docshell. If that fails, just return null and hope that the caller passed
264 if (nsCOMPtr
<nsIDocShell
> docShell
= GetDocShell()) {
265 nsCOMPtr
<nsPIDOMWindowOuter
> docShellWin
=
266 do_QueryInterface(docShell
->GetScriptGlobalObject());
268 doc
= docShellWin
->GetDoc();
272 return doc
? doc
->GetBaseURI() : nullptr;
275 } // namespace mozilla::dom