Bug 1852754: part 9) Add tests for dynamically loading <link rel="prefetch"> elements...
[gecko.git] / dom / base / LocationBase.cpp
blob38bcb32656e991dfbff2af0e9edd000eb594f9d1
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"
13 #include "nsCOMPtr.h"
14 #include "nsError.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);
29 return nullptr;
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);
41 return nullptr;
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
47 // anyway.
48 nsresult rv = ssm->CheckLoadURIWithPrincipal(
49 &aSubjectPrincipal, aURI, nsIScriptSecurityManager::STANDARD, 0);
50 if (NS_WARN_IF(NS_FAILED(rv))) {
51 nsAutoCString spec;
52 aURI->GetSpec(spec);
53 aRv.ThrowTypeError<MSG_URL_NOT_LOADABLE>(spec);
54 return nullptr;
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
62 // URI.
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;
76 // Create load info
77 RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
79 if (!doc) {
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);
97 if (urisEqual) {
98 referrerInfo = new ReferrerInfo(docCurrentURI, referrerPolicy);
99 } else {
100 principal->CreateReferrerInfo(referrerPolicy, getter_AddRefs(referrerInfo));
102 loadState->SetTriggeringPrincipal(triggeringPrincipal);
103 loadState->SetTriggeringSandboxFlags(doc->GetSandboxFlags());
104 loadState->SetCsp(doc->GetCsp());
105 if (referrerInfo) {
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()) {
118 return;
121 CallerType callerType = aSubjectPrincipal.IsSystemPrincipal()
122 ? CallerType::System
123 : CallerType::NonSystem;
125 nsresult rv = bc->CheckLocationChangeRateLimit(callerType);
126 if (NS_FAILED(rv)) {
127 aRv.Throw(rv);
128 return;
131 RefPtr<nsDocShellLoadState> loadState =
132 CheckURL(aURI, aSubjectPrincipal, aRv);
133 if (aRv.Failed()) {
134 return;
137 if (aReplace) {
138 loadState->SetLoadType(LOAD_STOP_CONTENT_AND_REPLACE);
139 } else {
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();
146 if (sourceWindow) {
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
166 // reasons as well.
168 // [1]:
169 // https://html.spec.whatwg.org/multipage/browsing-the-web.html#javascript-protocol
170 // [2]:
171 // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate
172 // [3]:
173 // https://html.spec.whatwg.org/multipage/browsers.html#allowed-to-navigate
174 return;
176 aRv.Throw(rv);
177 return;
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,
193 ErrorResult& aRv) {
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) {
202 nsresult result;
203 nsCOMPtr<nsIURI> newUri;
205 if (Document* doc = GetEntryDocument()) {
206 result = NS_NewURI(getter_AddRefs(newUri), aHref,
207 doc->GetDocumentCharacterSet(), aBase);
208 } else {
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);
215 return;
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());
230 if (win) {
231 scriptContext = nsGlobalWindowInner::Cast(win)->GetContextInternal();
234 if (scriptContext) {
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
262 // an absolute URI.
263 if (!doc) {
264 if (nsCOMPtr<nsIDocShell> docShell = GetDocShell()) {
265 nsCOMPtr<nsPIDOMWindowOuter> docShellWin =
266 do_QueryInterface(docShell->GetScriptGlobalObject());
267 if (docShellWin) {
268 doc = docShellWin->GetDoc();
272 return doc ? doc->GetBaseURI() : nullptr;
275 } // namespace mozilla::dom