Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/clients/manager/ClientNavigateOpChild.cpp
Line
Count
Source (jump to first uncovered line)
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/. */
6
7
#include "ClientNavigateOpChild.h"
8
9
#include "ClientState.h"
10
#include "mozilla/Unused.h"
11
#include "nsIDocShell.h"
12
#include "nsDocShellLoadInfo.h"
13
#include "nsIWebNavigation.h"
14
#include "nsIWebProgress.h"
15
#include "nsIWebProgressListener.h"
16
#include "nsNetUtil.h"
17
#include "nsPIDOMWindow.h"
18
#include "nsURLHelper.h"
19
20
namespace mozilla {
21
namespace dom {
22
23
namespace {
24
25
class NavigateLoadListener final : public nsIWebProgressListener
26
                                 , public nsSupportsWeakReference
27
{
28
  RefPtr<ClientOpPromise::Private> mPromise;
29
  RefPtr<nsPIDOMWindowOuter> mOuterWindow;
30
  nsCOMPtr<nsIURI> mBaseURL;
31
32
0
  ~NavigateLoadListener() = default;
33
34
public:
35
  NavigateLoadListener(ClientOpPromise::Private* aPromise,
36
                       nsPIDOMWindowOuter* aOuterWindow,
37
                       nsIURI* aBaseURL)
38
    : mPromise(aPromise)
39
    , mOuterWindow(aOuterWindow)
40
    , mBaseURL(aBaseURL)
41
0
  {
42
0
    MOZ_DIAGNOSTIC_ASSERT(mPromise);
43
0
    MOZ_DIAGNOSTIC_ASSERT(mOuterWindow);
44
0
    MOZ_DIAGNOSTIC_ASSERT(mBaseURL);
45
0
  }
46
47
  NS_IMETHOD
48
  OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
49
                uint32_t aStateFlags, nsresult aResult) override
50
0
  {
51
0
    if (!(aStateFlags & STATE_IS_DOCUMENT) ||
52
0
        !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
53
0
      return NS_OK;
54
0
    }
55
0
56
0
    aWebProgress->RemoveProgressListener(this);
57
0
58
0
    nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
59
0
    if (!channel) {
60
0
      mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
61
0
      return NS_OK;
62
0
    }
63
0
64
0
    nsCOMPtr<nsIURI> channelURL;
65
0
    nsresult rv = NS_GetFinalChannelURI(channel, getter_AddRefs(channelURL));
66
0
    if (NS_FAILED(rv)) {
67
0
      mPromise->Reject(rv, __func__);
68
0
      return NS_OK;
69
0
    }
70
0
71
0
    nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
72
0
    MOZ_DIAGNOSTIC_ASSERT(ssm);
73
0
74
0
    // If the resulting window is not same origin, then resolve immediately
75
0
    // without returning any information about the new Client.  This is
76
0
    // step 6.10 in the Client.navigate(url) spec.
77
0
    // todo: if you intend to update CheckSameOriginURI to log the error to the
78
0
    // console you also need to update the 'aFromPrivateWindow' argument.
79
0
    rv = ssm->CheckSameOriginURI(mBaseURL, channelURL, false, false);
80
0
    if (NS_FAILED(rv)) {
81
0
      mPromise->Resolve(NS_OK, __func__);
82
0
      return NS_OK;
83
0
    }
84
0
85
0
    nsPIDOMWindowInner* innerWindow = mOuterWindow->GetCurrentInnerWindow();
86
0
    MOZ_DIAGNOSTIC_ASSERT(innerWindow);
87
0
88
0
    Maybe<ClientInfo> clientInfo = innerWindow->GetClientInfo();
89
0
    MOZ_DIAGNOSTIC_ASSERT(clientInfo.isSome());
90
0
91
0
    Maybe<ClientState> clientState = innerWindow->GetClientState();
92
0
    MOZ_DIAGNOSTIC_ASSERT(clientState.isSome());
93
0
94
0
    // Otherwise, if the new window is same-origin we want to return a
95
0
    // ClientInfoAndState object so we can provide a Client snapshot
96
0
    // to the caller.  This is step 6.11 and 6.12 in the Client.navigate(url)
97
0
    // spec.
98
0
    mPromise->Resolve(ClientInfoAndState(clientInfo.ref().ToIPC(),
99
0
                                         clientState.ref().ToIPC()), __func__);
100
0
101
0
    return NS_OK;
102
0
  }
103
104
  NS_IMETHOD
105
  OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
106
                   int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
107
                   int32_t aCurTotalProgress, int32_t aMaxTotalProgress) override
108
0
  {
109
0
    MOZ_CRASH("Unexpected notification.");
110
0
    return NS_OK;
111
0
  }
112
113
  NS_IMETHOD
114
  OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
115
                   nsIURI* aLocation, uint32_t aFlags) override
116
0
  {
117
0
    MOZ_CRASH("Unexpected notification.");
118
0
    return NS_OK;
119
0
  }
120
121
  NS_IMETHOD
122
  OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
123
                 nsresult aStatus, const char16_t* aMessage) override
124
0
  {
125
0
    MOZ_CRASH("Unexpected notification.");
126
0
    return NS_OK;
127
0
  }
128
129
  NS_IMETHOD
130
  OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
131
                   uint32_t aState) override
132
0
  {
133
0
    MOZ_CRASH("Unexpected notification.");
134
0
    return NS_OK;
135
0
  }
136
137
  NS_DECL_ISUPPORTS
138
};
139
140
NS_IMPL_ISUPPORTS(NavigateLoadListener, nsIWebProgressListener,
141
                                        nsISupportsWeakReference);
142
143
} // anonymous namespace
144
145
already_AddRefed<ClientOpPromise>
146
ClientNavigateOpChild::DoNavigate(const ClientNavigateOpConstructorArgs& aArgs)
147
0
{
148
0
  RefPtr<ClientOpPromise> ref;
149
0
  nsCOMPtr<nsPIDOMWindowInner> window;
150
0
151
0
  // Navigating the target client window will result in the original
152
0
  // ClientSource being destroyed.  To avoid potential UAF mistakes
153
0
  // we use a small scope to access the ClientSource object.  Once
154
0
  // we have a strong reference to the window object we should not
155
0
  // access the ClientSource again.
156
0
  {
157
0
    ClientSourceChild* targetActor =
158
0
      static_cast<ClientSourceChild*>(aArgs.targetChild());
159
0
    MOZ_DIAGNOSTIC_ASSERT(targetActor);
160
0
161
0
    ClientSource* target = targetActor->GetSource();
162
0
    if (!target) {
163
0
      ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
164
0
                                             __func__);
165
0
      return ref.forget();
166
0
    }
167
0
168
0
    window = target->GetInnerWindow();
169
0
    if (!window) {
170
0
      ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
171
0
                                             __func__);
172
0
      return ref.forget();
173
0
    }
174
0
  }
175
0
176
0
  MOZ_ASSERT(NS_IsMainThread());
177
0
178
0
  mSerialEventTarget = window->EventTargetFor(TaskCategory::Other);
179
0
180
0
  // In theory we could do the URL work before paying the IPC overhead
181
0
  // cost, but in practice its easier to do it here.  The ClientHandle
182
0
  // may be off-main-thread while this method is guaranteed to always
183
0
  // be main thread.
184
0
  nsCOMPtr<nsIURI> baseURL;
185
0
  nsresult rv = NS_NewURI(getter_AddRefs(baseURL), aArgs.baseURL());
186
0
  if (NS_FAILED(rv)) {
187
0
    ref = ClientOpPromise::CreateAndReject(rv, __func__);
188
0
    return ref.forget();
189
0
  }
190
0
191
0
  // There is an edge case for view-source url here. According to the wpt test
192
0
  // windowclient-navigate.https.html, a view-source URL with a relative inner
193
0
  // URL should be treated as an invalid URL. However, we will still resolve it
194
0
  // into a valid view-source URL since the baseURL is involved while creating
195
0
  // the URI. So, an invalid view-source URL will be treated as a valid URL
196
0
  // in this case. To address this, we should not take the baseURL into account
197
0
  // for the view-source URL.
198
0
  bool shouldUseBaseURL = true;
199
0
  nsAutoCString scheme;
200
0
  if (NS_SUCCEEDED(net_ExtractURLScheme(aArgs.url(), scheme)) &&
201
0
      scheme.LowerCaseEqualsLiteral("view-source")) {
202
0
    shouldUseBaseURL = false;
203
0
  }
204
0
205
0
  nsCOMPtr<nsIURI> url;
206
0
  rv = NS_NewURI(getter_AddRefs(url), aArgs.url(),
207
0
                 nullptr, shouldUseBaseURL ? baseURL.get()
208
0
                                           : nullptr);
209
0
  if (NS_FAILED(rv)) {
210
0
    ref = ClientOpPromise::CreateAndReject(rv, __func__);
211
0
    return ref.forget();
212
0
  }
213
0
214
0
  if (url->GetSpecOrDefault().EqualsLiteral("about:blank")) {
215
0
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
216
0
    return ref.forget();
217
0
  }
218
0
219
0
  nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
220
0
  if (!doc || !doc->IsActive()) {
221
0
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
222
0
    return ref.forget();
223
0
  }
224
0
225
0
  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
226
0
  if (!principal) {
227
0
    ref = ClientOpPromise::CreateAndReject(rv, __func__);
228
0
    return ref.forget();
229
0
  }
230
0
231
0
  nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
232
0
  nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
233
0
  if (!docShell || !webProgress) {
234
0
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
235
0
    return ref.forget();
236
0
  }
237
0
238
0
  RefPtr<nsDocShellLoadInfo> loadInfo = new nsDocShellLoadInfo();
239
0
240
0
  loadInfo->SetTriggeringPrincipal(principal);
241
0
  loadInfo->SetReferrerPolicy(doc->GetReferrerPolicy());
242
0
  loadInfo->SetLoadType(LOAD_STOP_CONTENT);
243
0
  loadInfo->SetSourceDocShell(docShell);
244
0
  rv = docShell->LoadURI(url, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, true);
245
0
  if (NS_FAILED(rv)) {
246
0
    ref = ClientOpPromise::CreateAndReject(rv, __func__);
247
0
    return ref.forget();
248
0
  }
249
0
250
0
  RefPtr<ClientOpPromise::Private> promise =
251
0
    new ClientOpPromise::Private(__func__);
252
0
253
0
  nsCOMPtr<nsIWebProgressListener> listener =
254
0
    new NavigateLoadListener(promise, window->GetOuterWindow(), baseURL);
255
0
256
0
  rv = webProgress->AddProgressListener(listener,
257
0
                                        nsIWebProgress::NOTIFY_STATE_DOCUMENT);
258
0
  if (NS_FAILED(rv)) {
259
0
    promise->Reject(rv, __func__);
260
0
    ref = promise;
261
0
    return ref.forget();
262
0
  }
263
0
264
0
  ref = promise.get();
265
0
266
0
  ref->Then(mSerialEventTarget, __func__,
267
0
    [listener] (const ClientOpResult& aResult) { },
268
0
    [listener] (nsresult aResult) { });
269
0
270
0
  return ref.forget();
271
0
}
272
273
void
274
ClientNavigateOpChild::ActorDestroy(ActorDestroyReason aReason)
275
0
{
276
0
  mPromiseRequestHolder.DisconnectIfExists();
277
0
}
278
279
void
280
ClientNavigateOpChild::Init(const ClientNavigateOpConstructorArgs& aArgs)
281
0
{
282
0
  RefPtr<ClientOpPromise> promise = DoNavigate(aArgs);
283
0
284
0
  // Normally we get the event target from the window in DoNavigate().  If a
285
0
  // failure occurred, though, we may need to fall back to the current thread
286
0
  // target.
287
0
  if (!mSerialEventTarget) {
288
0
    mSerialEventTarget = GetCurrentThreadSerialEventTarget();
289
0
  }
290
0
291
0
  // Capturing `this` is safe here since we clear the mPromiseRequestHolder in
292
0
  // ActorDestroy.
293
0
  promise->Then(mSerialEventTarget, __func__,
294
0
    [this] (const ClientOpResult& aResult) {
295
0
      mPromiseRequestHolder.Complete();
296
0
      PClientNavigateOpChild::Send__delete__(this, aResult);
297
0
    }, [this] (nsresult aResult) {
298
0
      mPromiseRequestHolder.Complete();
299
0
      PClientNavigateOpChild::Send__delete__(this, aResult);
300
0
  })->Track(mPromiseRequestHolder);
301
0
}
302
303
} // namespace dom
304
} // namespace mozilla