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 loadState
->SetTriggeringWindowId(doc
->InnerWindowID());
112 loadState
->SetTriggeringStorageAccess(doc
->UsingStorageAccess());
114 return loadState
.forget();
117 void LocationBase::SetURI(nsIURI
* aURI
, nsIPrincipal
& aSubjectPrincipal
,
118 ErrorResult
& aRv
, bool aReplace
) {
119 RefPtr
<BrowsingContext
> bc
= GetBrowsingContext();
120 if (!bc
|| bc
->IsDiscarded()) {
124 CallerType callerType
= aSubjectPrincipal
.IsSystemPrincipal()
126 : CallerType::NonSystem
;
128 nsresult rv
= bc
->CheckLocationChangeRateLimit(callerType
);
134 RefPtr
<nsDocShellLoadState
> loadState
=
135 CheckURL(aURI
, aSubjectPrincipal
, aRv
);
141 loadState
->SetLoadType(LOAD_STOP_CONTENT_AND_REPLACE
);
143 loadState
->SetLoadType(LOAD_STOP_CONTENT
);
146 // Get the incumbent script's browsing context to set as source.
147 nsCOMPtr
<nsPIDOMWindowInner
> sourceWindow
=
148 nsContentUtils::IncumbentInnerWindow();
150 WindowContext
* context
= sourceWindow
->GetWindowContext();
151 loadState
->SetSourceBrowsingContext(sourceWindow
->GetBrowsingContext());
152 loadState
->SetHasValidUserGestureActivation(
153 context
&& context
->HasValidTransientUserGestureActivation());
156 loadState
->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE
);
157 loadState
->SetFirstParty(true);
159 rv
= bc
->LoadURI(loadState
);
160 if (NS_WARN_IF(NS_FAILED(rv
))) {
161 if (rv
== NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI
&&
162 net::SchemeIsJavascript(loadState
->URI())) {
163 // Per spec[1], attempting to load a javascript: URI into a cross-origin
164 // BrowsingContext is a no-op, and should not raise an exception.
165 // Technically, Location setters run with exceptions enabled should only
166 // throw an exception[2] when the caller is not allowed to navigate[3] the
167 // target browsing context due to sandboxing flags or not being
168 // closely-related enough, though in practice we currently throw for other
172 // https://html.spec.whatwg.org/multipage/browsing-the-web.html#javascript-protocol
174 // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate
176 // https://html.spec.whatwg.org/multipage/browsers.html#allowed-to-navigate
183 Document
* doc
= bc
->GetDocument();
184 if (doc
&& nsContentUtils::IsExternalProtocol(aURI
)) {
185 doc
->EnsureNotEnteringAndExitFullscreen();
189 void LocationBase::SetHref(const nsAString
& aHref
,
190 nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aRv
) {
191 DoSetHref(aHref
, aSubjectPrincipal
, false, aRv
);
194 void LocationBase::DoSetHref(const nsAString
& aHref
,
195 nsIPrincipal
& aSubjectPrincipal
, bool aReplace
,
197 // Get the source of the caller
198 nsCOMPtr
<nsIURI
> base
= GetSourceBaseURL();
199 SetHrefWithBase(aHref
, base
, aSubjectPrincipal
, aReplace
, aRv
);
202 void LocationBase::SetHrefWithBase(const nsAString
& aHref
, nsIURI
* aBase
,
203 nsIPrincipal
& aSubjectPrincipal
,
204 bool aReplace
, ErrorResult
& aRv
) {
206 nsCOMPtr
<nsIURI
> newUri
;
208 if (Document
* doc
= GetEntryDocument()) {
209 result
= NS_NewURI(getter_AddRefs(newUri
), aHref
,
210 doc
->GetDocumentCharacterSet(), aBase
);
212 result
= NS_NewURI(getter_AddRefs(newUri
), aHref
, nullptr, aBase
);
215 if (NS_FAILED(result
) || !newUri
) {
216 aRv
.ThrowSyntaxError("'"_ns
+ NS_ConvertUTF16toUTF8(aHref
) +
217 "' is not a valid URL."_ns
);
221 /* Check with the scriptContext if it is currently processing a script tag.
222 * If so, this must be a <script> tag with a location.href in it.
223 * we want to do a replace load, in such a situation.
224 * In other cases, for example if a event handler or a JS timer
225 * had a location.href in it, we want to do a normal load,
226 * so that the new url will be appended to Session History.
227 * This solution is tricky. Hopefully it isn't going to bite
228 * anywhere else. This is part of solution for bug # 39938, 72197
230 bool inScriptTag
= false;
231 nsIScriptContext
* scriptContext
= nullptr;
232 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(GetEntryGlobal());
234 scriptContext
= nsGlobalWindowInner::Cast(win
)->GetContextInternal();
238 if (scriptContext
->GetProcessingScriptTag()) {
239 // Now check to make sure that the script is running in our window,
240 // since we only want to replace if the location is set by a
241 // <script> tag in the same window. See bug 178729.
242 nsCOMPtr
<nsIDocShell
> docShell(GetDocShell());
243 nsCOMPtr
<nsIScriptGlobalObject
> ourGlobal
=
244 docShell
? docShell
->GetScriptGlobalObject() : nullptr;
245 inScriptTag
= (ourGlobal
== scriptContext
->GetGlobalObject());
249 SetURI(newUri
, aSubjectPrincipal
, aRv
, aReplace
|| inScriptTag
);
252 void LocationBase::Replace(const nsAString
& aUrl
,
253 nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aRv
) {
254 DoSetHref(aUrl
, aSubjectPrincipal
, true, aRv
);
257 nsIURI
* LocationBase::GetSourceBaseURL() {
258 Document
* doc
= GetEntryDocument();
260 // If there's no entry document, we either have no Script Entry Point or one
261 // that isn't a DOM Window. This doesn't generally happen with the DOM, but
262 // can sometimes happen with extension code in certain IPC configurations. If
263 // this happens, try falling back on the current document associated with the
264 // docshell. If that fails, just return null and hope that the caller passed
267 if (nsCOMPtr
<nsIDocShell
> docShell
= GetDocShell()) {
268 nsCOMPtr
<nsPIDOMWindowOuter
> docShellWin
=
269 do_QueryInterface(docShell
->GetScriptGlobalObject());
271 doc
= docShellWin
->GetDoc();
275 return doc
? doc
->GetBaseURI() : nullptr;
278 } // namespace mozilla::dom