/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 |