Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/clients/api/Clients.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 "Clients.h"
8
9
#include "ClientDOMUtil.h"
10
#include "mozilla/dom/ClientIPCTypes.h"
11
#include "mozilla/dom/ClientManager.h"
12
#include "mozilla/dom/ClientsBinding.h"
13
#include "mozilla/dom/Promise.h"
14
#include "mozilla/dom/ServiceWorkerDescriptor.h"
15
#include "mozilla/dom/ServiceWorkerManager.h"
16
#include "mozilla/dom/WorkerPrivate.h"
17
#include "mozilla/SystemGroup.h"
18
#include "nsIGlobalObject.h"
19
#include "nsString.h"
20
21
namespace mozilla {
22
namespace dom {
23
24
using mozilla::ipc::PrincipalInfo;
25
26
NS_IMPL_CYCLE_COLLECTING_ADDREF(Clients);
27
NS_IMPL_CYCLE_COLLECTING_RELEASE(Clients);
28
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Clients, mGlobal);
29
30
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Clients)
31
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
32
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
33
0
NS_INTERFACE_MAP_END
34
35
Clients::Clients(nsIGlobalObject* aGlobal)
36
  : mGlobal(aGlobal)
37
0
{
38
0
  MOZ_DIAGNOSTIC_ASSERT(mGlobal);
39
0
}
40
41
JSObject*
42
Clients::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
43
0
{
44
0
  return Clients_Binding::Wrap(aCx, this, aGivenProto);
45
0
}
46
47
nsIGlobalObject*
48
Clients::GetParentObject() const
49
0
{
50
0
  return mGlobal;
51
0
}
52
53
already_AddRefed<Promise>
54
Clients::Get(const nsAString& aClientID, ErrorResult& aRv)
55
0
{
56
0
  MOZ_ASSERT(!NS_IsMainThread());
57
0
  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
58
0
  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
59
0
  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
60
0
  workerPrivate->AssertIsOnWorkerThread();
61
0
62
0
  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
63
0
  if (aRv.Failed()) {
64
0
    return outerPromise.forget();
65
0
  }
66
0
67
0
  nsID id;
68
0
  // nsID::Parse accepts both "{...}" and "...", but we only emit the latter, so
69
0
  // forbid strings that start with "{" to avoid inconsistency and bugs like
70
0
  // bug 1446225.
71
0
  if (aClientID.IsEmpty() || aClientID.CharAt(0) == '{' ||
72
0
      !id.Parse(NS_ConvertUTF16toUTF8(aClientID).get())) {
73
0
    // Invalid ID means we will definitely not find a match, so just
74
0
    // resolve with undefined indicating "not found".
75
0
    outerPromise->MaybeResolveWithUndefined();
76
0
    return outerPromise.forget();
77
0
  }
78
0
79
0
  const PrincipalInfo& principalInfo = workerPrivate->GetPrincipalInfo();
80
0
  nsCOMPtr<nsISerialEventTarget> target =
81
0
    mGlobal->EventTargetFor(TaskCategory::Other);
82
0
83
0
  RefPtr<ClientOpPromise> innerPromise =
84
0
    ClientManager::GetInfoAndState(ClientGetInfoAndStateArgs(id, principalInfo),
85
0
                                   target);
86
0
87
0
  nsCString scope = workerPrivate->ServiceWorkerScope();
88
0
  auto holder = MakeRefPtr<DOMMozPromiseRequestHolder<ClientOpPromise>>(mGlobal);
89
0
90
0
  innerPromise->Then(target, __func__,
91
0
    [outerPromise, holder, scope] (const ClientOpResult& aResult) {
92
0
      holder->Complete();
93
0
      NS_ENSURE_TRUE_VOID(holder->GetParentObject());
94
0
      RefPtr<Client> client = new Client(holder->GetParentObject(),
95
0
                                         aResult.get_ClientInfoAndState());
96
0
      if (client->GetStorageAccess() == nsContentUtils::StorageAccess::eAllow) {
97
0
        outerPromise->MaybeResolve(std::move(client));
98
0
        return;
99
0
      }
100
0
      nsCOMPtr<nsIRunnable> r =
101
0
        NS_NewRunnableFunction("Clients::Get() storage denied",
102
0
        [scope] {
103
0
          ServiceWorkerManager::LocalizeAndReportToAllClients(
104
0
            scope, "ServiceWorkerGetClientStorageError", nsTArray<nsString>());
105
0
        });
106
0
      SystemGroup::Dispatch(TaskCategory::Other, r.forget());
107
0
      outerPromise->MaybeResolveWithUndefined();
108
0
    }, [outerPromise, holder] (nsresult aResult) {
109
0
      holder->Complete();
110
0
      outerPromise->MaybeResolveWithUndefined();
111
0
    })->Track(*holder);
112
0
113
0
  return outerPromise.forget();
114
0
}
115
116
namespace {
117
118
class MatchAllComparator final
119
{
120
public:
121
  bool
122
  LessThan(Client* aLeft, Client* aRight) const
123
0
  {
124
0
    TimeStamp leftFocusTime = aLeft->LastFocusTime();
125
0
    TimeStamp rightFocusTime = aRight->LastFocusTime();
126
0
    // If the focus times are the same, then default to creation order.
127
0
    // MatchAll should return oldest Clients first.
128
0
    if (leftFocusTime == rightFocusTime) {
129
0
      return aLeft->CreationTime() < aRight->CreationTime();
130
0
    }
131
0
132
0
    // Otherwise compare focus times.  We reverse the logic here so
133
0
    // that the most recently focused window is first in the list.
134
0
    if (!leftFocusTime.IsNull() && rightFocusTime.IsNull()) {
135
0
      return true;
136
0
    }
137
0
    if (leftFocusTime.IsNull() && !rightFocusTime.IsNull()) {
138
0
      return false;
139
0
    }
140
0
    return leftFocusTime > rightFocusTime;
141
0
  }
142
143
  bool
144
  Equals(Client* aLeft, Client* aRight) const
145
0
  {
146
0
    return aLeft->LastFocusTime() == aRight->LastFocusTime() &&
147
0
           aLeft->CreationTime() == aRight->CreationTime();
148
0
  }
149
};
150
151
} // anonymous namespace
152
153
already_AddRefed<Promise>
154
Clients::MatchAll(const ClientQueryOptions& aOptions, ErrorResult& aRv)
155
0
{
156
0
  MOZ_ASSERT(!NS_IsMainThread());
157
0
  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
158
0
  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
159
0
  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
160
0
  workerPrivate->AssertIsOnWorkerThread();
161
0
162
0
  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
163
0
  if (aRv.Failed()) {
164
0
    return outerPromise.forget();
165
0
  }
166
0
167
0
  nsCOMPtr<nsIGlobalObject> global = mGlobal;
168
0
  nsCString scope = workerPrivate->ServiceWorkerScope();
169
0
170
0
  ClientMatchAllArgs args(workerPrivate->GetServiceWorkerDescriptor().ToIPC(),
171
0
                          aOptions.mType,
172
0
                          aOptions.mIncludeUncontrolled);
173
0
  StartClientManagerOp(&ClientManager::MatchAll, args, mGlobal,
174
0
    [outerPromise, global, scope] (const ClientOpResult& aResult) {
175
0
      nsTArray<RefPtr<Client>> clientList;
176
0
      bool storageDenied = false;
177
0
      for (const ClientInfoAndState& value : aResult.get_ClientList().values()) {
178
0
        RefPtr<Client> client = new Client(global, value);
179
0
        if (client->GetStorageAccess() != nsContentUtils::StorageAccess::eAllow) {
180
0
          storageDenied = true;
181
0
          continue;
182
0
        }
183
0
        clientList.AppendElement(std::move(client));
184
0
      }
185
0
      if (storageDenied) {
186
0
        nsCOMPtr<nsIRunnable> r =
187
0
          NS_NewRunnableFunction("Clients::MatchAll() storage denied",
188
0
          [scope] {
189
0
            ServiceWorkerManager::LocalizeAndReportToAllClients(
190
0
              scope, "ServiceWorkerGetClientStorageError", nsTArray<nsString>());
191
0
          });
192
0
        SystemGroup::Dispatch(TaskCategory::Other, r.forget());
193
0
      }
194
0
      clientList.Sort(MatchAllComparator());
195
0
      outerPromise->MaybeResolve(clientList);
196
0
    }, [outerPromise] (nsresult aResult) {
197
0
      outerPromise->MaybeReject(aResult);
198
0
    });
199
0
200
0
  return outerPromise.forget();
201
0
}
202
203
already_AddRefed<Promise>
204
Clients::OpenWindow(const nsAString& aURL, ErrorResult& aRv)
205
0
{
206
0
  MOZ_ASSERT(!NS_IsMainThread());
207
0
  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
208
0
  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
209
0
  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
210
0
  workerPrivate->AssertIsOnWorkerThread();
211
0
212
0
  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
213
0
  if (aRv.Failed()) {
214
0
    return outerPromise.forget();
215
0
  }
216
0
217
0
  if (aURL.EqualsLiteral("about:blank")) {
218
0
    // TODO: Improve this error in bug 1412856.
219
0
    outerPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR);
220
0
    return outerPromise.forget();
221
0
  }
222
0
223
0
  if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
224
0
    outerPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
225
0
    return outerPromise.forget();
226
0
  }
227
0
228
0
  const PrincipalInfo& principalInfo = workerPrivate->GetPrincipalInfo();
229
0
  nsCString baseURL = workerPrivate->GetLocationInfo().mHref;
230
0
  ClientOpenWindowArgs args(principalInfo, NS_ConvertUTF16toUTF8(aURL),
231
0
                            baseURL);
232
0
233
0
  nsCOMPtr<nsIGlobalObject> global = mGlobal;
234
0
235
0
  StartClientManagerOp(&ClientManager::OpenWindow, args, mGlobal,
236
0
    [outerPromise, global] (const ClientOpResult& aResult) {
237
0
      if (aResult.type() != ClientOpResult::TClientInfoAndState) {
238
0
        outerPromise->MaybeResolve(JS::NullHandleValue);
239
0
        return;
240
0
      }
241
0
      RefPtr<Client> client =
242
0
        new Client(global, aResult.get_ClientInfoAndState());
243
0
      outerPromise->MaybeResolve(client);
244
0
    }, [outerPromise] (nsresult aResult) {
245
0
      // TODO: Improve this error in bug 1412856.  Ideally we should throw
246
0
      //       the TypeError in the child process and pass it back to here.
247
0
      outerPromise->MaybeReject(NS_ERROR_TYPE_ERR);
248
0
    });
249
0
250
0
  return outerPromise.forget();
251
0
}
252
253
already_AddRefed<Promise>
254
Clients::Claim(ErrorResult& aRv)
255
0
{
256
0
  MOZ_ASSERT(!NS_IsMainThread());
257
0
  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
258
0
  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
259
0
  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
260
0
  workerPrivate->AssertIsOnWorkerThread();
261
0
262
0
  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
263
0
  if (aRv.Failed()) {
264
0
    return outerPromise.forget();
265
0
  }
266
0
267
0
  const ServiceWorkerDescriptor& serviceWorker =
268
0
    workerPrivate->GetServiceWorkerDescriptor();
269
0
270
0
  if (serviceWorker.State() != ServiceWorkerState::Activating &&
271
0
      serviceWorker.State() != ServiceWorkerState::Activated) {
272
0
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
273
0
    return outerPromise.forget();
274
0
  }
275
0
276
0
  StartClientManagerOp(
277
0
    &ClientManager::Claim, ClientClaimArgs(serviceWorker.ToIPC()), mGlobal,
278
0
    [outerPromise] (const ClientOpResult& aResult) {
279
0
      outerPromise->MaybeResolveWithUndefined();
280
0
    }, [outerPromise] (nsresult aResult) {
281
0
      outerPromise->MaybeReject(aResult);
282
0
    });
283
0
284
0
  return outerPromise.forget();
285
0
}
286
287
} // namespace dom
288
} // namespace mozilla