Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/clients/manager/ClientOpenWindowUtils.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 "ClientOpenWindowUtils.h"
8
9
#include "ClientInfo.h"
10
#include "ClientState.h"
11
#include "mozilla/SystemGroup.h"
12
#include "nsContentUtils.h"
13
#include "nsIBrowserDOMWindow.h"
14
#include "nsIDocShell.h"
15
#include "nsIDOMChromeWindow.h"
16
#include "nsIURI.h"
17
#include "nsIWebProgress.h"
18
#include "nsIWebProgressListener.h"
19
#include "nsIWindowWatcher.h"
20
#include "nsIXPConnect.h"
21
#include "nsNetUtil.h"
22
#include "nsPIDOMWindow.h"
23
#include "nsPIWindowWatcher.h"
24
25
#ifdef MOZ_WIDGET_ANDROID
26
#include "FennecJNIWrappers.h"
27
#endif
28
29
namespace mozilla {
30
namespace dom {
31
32
namespace {
33
34
class WebProgressListener final : public nsIWebProgressListener
35
                                , public nsSupportsWeakReference
36
{
37
public:
38
  NS_DECL_ISUPPORTS
39
40
  WebProgressListener(nsPIDOMWindowOuter* aWindow,
41
                      nsIURI* aBaseURI,
42
                      already_AddRefed<ClientOpPromise::Private> aPromise)
43
  : mPromise(aPromise)
44
  , mWindow(aWindow)
45
  , mBaseURI(aBaseURI)
46
0
  {
47
0
    MOZ_ASSERT(aWindow);
48
0
    MOZ_ASSERT(aBaseURI);
49
0
    MOZ_ASSERT(NS_IsMainThread());
50
0
  }
51
52
  NS_IMETHOD
53
  OnStateChange(nsIWebProgress* aWebProgress,
54
                nsIRequest* aRequest,
55
                uint32_t aStateFlags, nsresult aStatus) override
56
0
  {
57
0
    if (!(aStateFlags & STATE_IS_DOCUMENT) ||
58
0
         !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
59
0
      return NS_OK;
60
0
    }
61
0
62
0
    // Our caller keeps a strong reference, so it is safe to remove the listener
63
0
    // from ServiceWorkerPrivate.
64
0
    aWebProgress->RemoveProgressListener(this);
65
0
66
0
    nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
67
0
    if (NS_WARN_IF(!doc)) {
68
0
      mPromise->Reject(NS_ERROR_FAILURE, __func__);
69
0
      mPromise = nullptr;
70
0
      return NS_OK;
71
0
    }
72
0
73
0
    // Check same origin.
74
0
    nsCOMPtr<nsIScriptSecurityManager> securityManager =
75
0
      nsContentUtils::GetSecurityManager();
76
0
    bool isPrivateWin =
77
0
      doc->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0;
78
0
    nsresult rv = securityManager->CheckSameOriginURI(doc->GetOriginalURI(),
79
0
                                                      mBaseURI, false, isPrivateWin);
80
0
    if (NS_FAILED(rv)) {
81
0
      mPromise->Resolve(NS_OK, __func__);
82
0
      mPromise = nullptr;
83
0
      return NS_OK;
84
0
    }
85
0
86
0
    Maybe<ClientInfo> info(doc->GetClientInfo());
87
0
    Maybe<ClientState> state(doc->GetClientState());
88
0
89
0
    if (NS_WARN_IF(info.isNothing() || state.isNothing())) {
90
0
      mPromise->Reject(NS_ERROR_FAILURE, __func__);
91
0
      mPromise = nullptr;
92
0
      return NS_OK;
93
0
    }
94
0
95
0
    mPromise->Resolve(ClientInfoAndState(info.ref().ToIPC(), state.ref().ToIPC()),
96
0
                      __func__);
97
0
    mPromise = nullptr;
98
0
99
0
    return NS_OK;
100
0
  }
101
102
  NS_IMETHOD
103
  OnProgressChange(nsIWebProgress* aWebProgress,
104
                   nsIRequest* aRequest,
105
                   int32_t aCurSelfProgress,
106
                   int32_t aMaxSelfProgress,
107
                   int32_t aCurTotalProgress,
108
                   int32_t aMaxTotalProgress) override
109
0
  {
110
0
    MOZ_ASSERT(false, "Unexpected notification.");
111
0
    return NS_OK;
112
0
  }
113
114
  NS_IMETHOD
115
  OnLocationChange(nsIWebProgress* aWebProgress,
116
                   nsIRequest* aRequest,
117
                   nsIURI* aLocation,
118
                   uint32_t aFlags) override
119
0
  {
120
0
    MOZ_ASSERT(false, "Unexpected notification.");
121
0
    return NS_OK;
122
0
  }
123
124
  NS_IMETHOD
125
  OnStatusChange(nsIWebProgress* aWebProgress,
126
                 nsIRequest* aRequest,
127
                 nsresult aStatus, const char16_t* aMessage) override
128
0
  {
129
0
    MOZ_ASSERT(false, "Unexpected notification.");
130
0
    return NS_OK;
131
0
  }
132
133
  NS_IMETHOD
134
  OnSecurityChange(nsIWebProgress* aWebProgress,
135
                   nsIRequest* aRequest,
136
                   uint32_t aState) override
137
0
  {
138
0
    MOZ_ASSERT(false, "Unexpected notification.");
139
0
    return NS_OK;
140
0
  }
141
142
private:
143
  ~WebProgressListener()
144
0
  {
145
0
    if (mPromise) {
146
0
      mPromise->Reject(NS_ERROR_ABORT, __func__);
147
0
      mPromise = nullptr;
148
0
    }
149
0
  }
150
151
  RefPtr<ClientOpPromise::Private> mPromise;
152
  // TODO: make window a weak ref and stop cycle collecting
153
  nsCOMPtr<nsPIDOMWindowOuter> mWindow;
154
  nsCOMPtr<nsIURI> mBaseURI;
155
};
156
157
NS_IMPL_ISUPPORTS(WebProgressListener, nsIWebProgressListener,
158
                                       nsISupportsWeakReference);
159
160
nsresult
161
OpenWindow(const ClientOpenWindowArgs& aArgs,
162
           nsPIDOMWindowOuter** aWindow)
163
0
{
164
0
  MOZ_DIAGNOSTIC_ASSERT(aWindow);
165
0
166
0
  // [[1. Let url be the result of parsing url with entry settings object's API
167
0
  //   base URL.]]
168
0
  nsCOMPtr<nsIURI> uri;
169
0
170
0
  nsCOMPtr<nsIURI> baseURI;
171
0
  nsresult rv = NS_NewURI(getter_AddRefs(baseURI), aArgs.baseURL());
172
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
173
0
    return NS_ERROR_TYPE_ERR;
174
0
  }
175
0
176
0
  rv = NS_NewURI(getter_AddRefs(uri), aArgs.url(), nullptr, baseURI);
177
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
178
0
    return NS_ERROR_TYPE_ERR;
179
0
  }
180
0
181
0
  nsCOMPtr<nsIPrincipal> principal =
182
0
    PrincipalInfoToPrincipal(aArgs.principalInfo());
183
0
  MOZ_DIAGNOSTIC_ASSERT(principal);
184
0
185
0
  // [[6.1 Open Window]]
186
0
  if (XRE_IsContentProcess()) {
187
0
188
0
    // Let's create a sandbox in order to have a valid JSContext and correctly
189
0
    // propagate the SubjectPrincipal.
190
0
    AutoJSAPI jsapi;
191
0
    jsapi.Init();
192
0
193
0
    JSContext* cx = jsapi.cx();
194
0
195
0
    nsIXPConnect* xpc = nsContentUtils::XPConnect();
196
0
    MOZ_DIAGNOSTIC_ASSERT(xpc);
197
0
198
0
    JS::Rooted<JSObject*> sandbox(cx);
199
0
    rv = xpc->CreateSandbox(cx, principal, sandbox.address());
200
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
201
0
      return NS_ERROR_TYPE_ERR;
202
0
    }
203
0
204
0
    JSAutoRealm ar(cx, sandbox);
205
0
206
0
    // ContentProcess
207
0
    nsCOMPtr<nsIWindowWatcher> wwatch =
208
0
      do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
209
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
210
0
      return rv;
211
0
    }
212
0
    nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
213
0
    NS_ENSURE_STATE(pwwatch);
214
0
215
0
    nsCString spec;
216
0
    rv = uri->GetSpec(spec);
217
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
218
0
      return rv;
219
0
    }
220
0
221
0
    nsCOMPtr<mozIDOMWindowProxy> newWindow;
222
0
    rv = pwwatch->OpenWindow2(nullptr,
223
0
                              spec.get(),
224
0
                              nullptr,
225
0
                              nullptr,
226
0
                              false, false, true, nullptr,
227
0
                              // Not a spammy popup; we got permission, we swear!
228
0
                              /* aIsPopupSpam = */ false,
229
0
                              // Don't force noopener.  We're not passing in an
230
0
                              // opener anyway, and we _do_ want the returned
231
0
                              // window.
232
0
                              /* aForceNoOpener = */ false,
233
0
                              /* aLoadInfp = */ nullptr,
234
0
                              getter_AddRefs(newWindow));
235
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
236
0
      return rv;
237
0
    }
238
0
    nsCOMPtr<nsPIDOMWindowOuter> pwindow = nsPIDOMWindowOuter::From(newWindow);
239
0
    pwindow.forget(aWindow);
240
0
    MOZ_DIAGNOSTIC_ASSERT(*aWindow);
241
0
    return NS_OK;
242
0
  }
243
0
244
0
  // Find the most recent browser window and open a new tab in it.
245
0
  nsCOMPtr<nsPIDOMWindowOuter> browserWindow =
246
0
    nsContentUtils::GetMostRecentNonPBWindow();
247
0
  if (!browserWindow) {
248
0
    // It is possible to be running without a browser window on Mac OS, so
249
0
    // we need to open a new chrome window.
250
0
    // TODO(catalinb): open new chrome window. Bug 1218080
251
0
    return NS_ERROR_NOT_AVAILABLE;
252
0
  }
253
0
254
0
  nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(browserWindow);
255
0
  if (NS_WARN_IF(!chromeWin)) {
256
0
    return NS_ERROR_FAILURE;
257
0
  }
258
0
259
0
  nsCOMPtr<nsIBrowserDOMWindow> bwin;
260
0
  chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));
261
0
262
0
  if (NS_WARN_IF(!bwin)) {
263
0
    return NS_ERROR_FAILURE;
264
0
  }
265
0
266
0
  nsCOMPtr<mozIDOMWindowProxy> win;
267
0
  rv = bwin->OpenURI(uri, nullptr,
268
0
                     nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW,
269
0
                     nsIBrowserDOMWindow::OPEN_NEW,
270
0
                     principal,
271
0
                     getter_AddRefs(win));
272
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
273
0
    return rv;
274
0
  }
275
0
  NS_ENSURE_STATE(win);
276
0
277
0
  nsCOMPtr<nsPIDOMWindowOuter> pWin = nsPIDOMWindowOuter::From(win);
278
0
  pWin.forget(aWindow);
279
0
  MOZ_DIAGNOSTIC_ASSERT(*aWindow);
280
0
281
0
  return NS_OK;
282
0
}
283
284
void
285
WaitForLoad(const ClientOpenWindowArgs& aArgs,
286
            nsPIDOMWindowOuter* aOuterWindow,
287
            ClientOpPromise::Private* aPromise)
288
0
{
289
0
  MOZ_DIAGNOSTIC_ASSERT(aOuterWindow);
290
0
291
0
  RefPtr<ClientOpPromise::Private> promise = aPromise;
292
0
293
0
  nsresult rv = nsContentUtils::DispatchFocusChromeEvent(aOuterWindow);
294
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
295
0
    promise->Reject(rv, __func__);
296
0
    return;
297
0
  }
298
0
299
0
  nsCOMPtr<nsIURI> baseURI;
300
0
  rv = NS_NewURI(getter_AddRefs(baseURI), aArgs.baseURL());
301
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
302
0
    promise->Reject(rv, __func__);
303
0
    return;
304
0
  }
305
0
306
0
  nsCOMPtr<nsIDocShell> docShell = aOuterWindow->GetDocShell();
307
0
  nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
308
0
309
0
  if (NS_WARN_IF(!webProgress)) {
310
0
    promise->Reject(NS_ERROR_FAILURE, __func__);
311
0
    return;
312
0
  }
313
0
314
0
  RefPtr<ClientOpPromise> ref = promise;
315
0
316
0
  RefPtr<WebProgressListener> listener =
317
0
    new WebProgressListener(aOuterWindow, baseURI, promise.forget());
318
0
319
0
320
0
  rv = webProgress->AddProgressListener(listener,
321
0
                                        nsIWebProgress::NOTIFY_STATE_DOCUMENT);
322
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
323
0
    promise->Reject(rv, __func__);
324
0
    return;
325
0
  }
326
0
327
0
  // Hold the listener alive until the promise settles
328
0
  ref->Then(aOuterWindow->EventTargetFor(TaskCategory::Other), __func__,
329
0
    [listener] (const ClientOpResult& aResult) { },
330
0
    [listener] (nsresult aResult) { });
331
0
}
332
333
#ifdef MOZ_WIDGET_ANDROID
334
335
class LaunchObserver final : public nsIObserver
336
{
337
  RefPtr<GenericPromise::Private> mPromise;
338
339
  LaunchObserver()
340
    : mPromise(new GenericPromise::Private(__func__))
341
  {
342
  }
343
344
  ~LaunchObserver() = default;
345
346
  NS_IMETHOD
347
  Observe(nsISupports* aSubject, const char* aTopic, const char16_t * aData) override
348
  {
349
    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
350
    if (os) {
351
      os->RemoveObserver(this, "BrowserChrome:Ready");
352
    }
353
    mPromise->Resolve(true, __func__);
354
    return NS_OK;
355
  }
356
357
public:
358
  static already_AddRefed<LaunchObserver>
359
  Create()
360
  {
361
    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
362
    if (NS_WARN_IF(!os)) {
363
      return nullptr;
364
    }
365
366
    RefPtr<LaunchObserver> ref = new LaunchObserver();
367
368
    nsresult rv = os->AddObserver(ref, "BrowserChrome:Ready", /* weakRef */ false);
369
    if (NS_WARN_IF(NS_FAILED(rv))) {
370
      return nullptr;
371
    }
372
373
    return ref.forget();
374
  }
375
376
  void
377
  Cancel()
378
  {
379
    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
380
    if (os) {
381
      os->RemoveObserver(this, "BrowserChrome:Ready");
382
    }
383
    mPromise->Reject(NS_ERROR_ABORT, __func__);
384
  }
385
386
  GenericPromise*
387
  Promise()
388
  {
389
    return mPromise;
390
  }
391
392
  NS_DECL_ISUPPORTS
393
};
394
395
NS_IMPL_ISUPPORTS(LaunchObserver, nsIObserver);
396
397
#endif // MOZ_WIDGET_ANDROID
398
399
} // anonymous namespace
400
401
already_AddRefed<ClientOpPromise>
402
ClientOpenWindowInCurrentProcess(const ClientOpenWindowArgs& aArgs)
403
0
{
404
0
  RefPtr<ClientOpPromise::Private> promise =
405
0
    new ClientOpPromise::Private(__func__);
406
0
  RefPtr<ClientOpPromise> ref = promise;
407
0
408
#ifdef MOZ_WIDGET_ANDROID
409
  // This fires an intent that will start launching Fennec and foreground it,
410
  // if necessary.  We create an observer so that we can determine when
411
  // the launch has completed.
412
  RefPtr<LaunchObserver> launchObserver = LaunchObserver::Create();
413
  java::GeckoApp::LaunchOrBringToFront();
414
#endif // MOZ_WIDGET_ANDROID
415
416
0
  nsCOMPtr<nsPIDOMWindowOuter> outerWindow;
417
0
  nsresult rv = OpenWindow(aArgs, getter_AddRefs(outerWindow));
418
0
419
#ifdef MOZ_WIDGET_ANDROID
420
  // If we get the NOT_AVAILABLE error that means the browser is still
421
  // launching on android.  Use the observer we created above to wait
422
  // until the launch completes and then try to open the window again.
423
  if (rv == NS_ERROR_NOT_AVAILABLE && launchObserver) {
424
    RefPtr<GenericPromise> p = launchObserver->Promise();
425
    p->Then(SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
426
      [aArgs, promise] (bool aResult) {
427
        nsCOMPtr<nsPIDOMWindowOuter> outerWindow;
428
        nsresult rv = OpenWindow(aArgs, getter_AddRefs(outerWindow));
429
        if (NS_WARN_IF(NS_FAILED(rv))) {
430
          promise->Reject(rv, __func__);
431
        }
432
433
        WaitForLoad(aArgs, outerWindow, promise);
434
      }, [promise] (nsresult aResult) {
435
        promise->Reject(aResult, __func__);
436
      });
437
    return ref.forget();
438
  }
439
440
  // If we didn't get the NOT_AVAILABLE error then there is no need
441
  // wait for the browser to launch.  Cancel the observer so that it
442
  // will release.
443
  if (launchObserver) {
444
    launchObserver->Cancel();
445
  }
446
#endif // MOZ_WIDGET_ANDROID
447
448
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
449
0
    promise->Reject(rv, __func__);
450
0
    return ref.forget();
451
0
  }
452
0
453
0
  MOZ_DIAGNOSTIC_ASSERT(outerWindow);
454
0
  WaitForLoad(aArgs, outerWindow, promise);
455
0
456
0
  return ref.forget();
457
0
}
458
459
} // namespace dom
460
} // namespace mozilla