Worker.cpp (9016B)
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 "Worker.h" 8 9 #include "EventWithOptionsRunnable.h" 10 #include "MessageEventRunnable.h" 11 #include "WorkerPrivate.h" 12 #include "js/RootingAPI.h" 13 #include "mozilla/ProfilerLabels.h" 14 #include "mozilla/ProfilerMarkers.h" 15 #include "mozilla/RefPtr.h" 16 #include "mozilla/dom/BindingDeclarations.h" 17 #include "mozilla/dom/TrustedScriptURL.h" 18 #include "mozilla/dom/TrustedTypeUtils.h" 19 #include "mozilla/dom/TrustedTypesConstants.h" 20 #include "mozilla/dom/WorkerBinding.h" 21 #include "mozilla/dom/WorkerStatus.h" 22 #include "nsContentUtils.h" 23 #include "nsDebug.h" 24 #include "nsGlobalWindowInner.h" 25 #include "nsISupports.h" 26 27 #ifdef XP_WIN 28 # undef PostMessage 29 #endif 30 31 namespace mozilla::dom { 32 33 /* static */ 34 already_AddRefed<Worker> Worker::Constructor( 35 const GlobalObject& aGlobal, const TrustedScriptURLOrUSVString& aScriptURL, 36 const WorkerOptions& aOptions, ErrorResult& aRv) { 37 JSContext* cx = aGlobal.Context(); 38 39 nsCOMPtr<nsIGlobalObject> globalObject = 40 do_QueryInterface(aGlobal.GetAsSupports()); 41 42 nsPIDOMWindowInner* innerWindow = globalObject->GetAsInnerWindow(); 43 if (innerWindow && !innerWindow->IsCurrentInnerWindow()) { 44 aRv.ThrowInvalidStateError( 45 "Cannot create worker for a going to be discarded document"); 46 return nullptr; 47 } 48 49 // TODO(Bug 1963277) This doen't work for content scripts. 50 nsCOMPtr<nsIPrincipal> principal = aGlobal.GetSubjectPrincipal(); 51 52 // The spec only mentions Window and WorkerGlobalScope global objects, but 53 // Gecko can actually call the constructor with other ones, so we just skip 54 // trusted types handling in that case. 55 // https://html.spec.whatwg.org/multipage/workers.html#dedicated-workers-and-the-worker-interface 56 const nsAString* compliantString = nullptr; 57 bool performTrustedTypeConversion = innerWindow; 58 if (!performTrustedTypeConversion) { 59 if (JSObject* globalJSObject = globalObject->GetGlobalJSObject()) { 60 performTrustedTypeConversion = IsWorkerGlobal(globalJSObject); 61 } 62 } 63 Maybe<nsAutoString> compliantStringHolder; 64 if (performTrustedTypeConversion) { 65 constexpr nsLiteralString sink = u"Worker constructor"_ns; 66 compliantString = TrustedTypeUtils::GetTrustedTypesCompliantString( 67 aScriptURL, sink, kTrustedTypesOnlySinkGroup, *globalObject, principal, 68 compliantStringHolder, aRv); 69 if (aRv.Failed()) { 70 return nullptr; 71 } 72 } else { 73 compliantString = aScriptURL.IsUSVString() 74 ? &aScriptURL.GetAsUSVString() 75 : &aScriptURL.GetAsTrustedScriptURL().mData; 76 } 77 MOZ_ASSERT(compliantString); 78 79 RefPtr<WorkerPrivate> workerPrivate = WorkerPrivate::Constructor( 80 cx, *compliantString, false /* aIsChromeWorker */, WorkerKindDedicated, 81 aOptions.mCredentials, aOptions.mType, aOptions.mName, VoidCString(), 82 nullptr /*aLoadInfo */, aRv); 83 if (NS_WARN_IF(aRv.Failed())) { 84 return nullptr; 85 } 86 87 RefPtr<Worker> worker = new Worker(globalObject, workerPrivate.forget()); 88 return worker.forget(); 89 } 90 91 Worker::Worker(nsIGlobalObject* aGlobalObject, 92 already_AddRefed<WorkerPrivate> aWorkerPrivate) 93 : DOMEventTargetHelper(aGlobalObject), 94 mWorkerPrivate(std::move(aWorkerPrivate)) { 95 MOZ_ASSERT(mWorkerPrivate); 96 mWorkerPrivate->SetParentEventTargetRef(this); 97 } 98 99 Worker::~Worker() { Terminate(); } 100 101 JSObject* Worker::WrapObject(JSContext* aCx, 102 JS::Handle<JSObject*> aGivenProto) { 103 JS::Rooted<JSObject*> wrapper(aCx, 104 Worker_Binding::Wrap(aCx, this, aGivenProto)); 105 if (wrapper) { 106 // Most DOM objects don't assume they have a reflector. If they don't have 107 // one and need one, they create it. But in workers code, we assume that the 108 // reflector is always present. In order to guarantee that it's always 109 // present, we have to preserve it. Otherwise the GC will happily collect it 110 // as needed. 111 MOZ_ALWAYS_TRUE(TryPreserveWrapper(wrapper)); 112 } 113 114 return wrapper; 115 } 116 117 bool Worker::IsEligibleForMessaging() { 118 NS_ASSERT_OWNINGTHREAD(Worker); 119 120 return mWorkerPrivate && mWorkerPrivate->ParentStatusProtected() <= Running; 121 } 122 123 void Worker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, 124 const Sequence<JSObject*>& aTransferable, 125 ErrorResult& aRv) { 126 NS_ASSERT_OWNINGTHREAD(Worker); 127 128 if (!mWorkerPrivate || mWorkerPrivate->ParentStatusProtected() > Running) { 129 return; 130 } 131 RefPtr<WorkerPrivate> workerPrivate = mWorkerPrivate; 132 (void)workerPrivate; 133 134 JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue()); 135 136 aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable, 137 &transferable); 138 if (NS_WARN_IF(aRv.Failed())) { 139 return; 140 } 141 142 NS_ConvertUTF16toUTF8 nameOrScriptURL( 143 mWorkerPrivate->WorkerName().IsEmpty() 144 ? Substring( 145 mWorkerPrivate->ScriptURL(), 0, 146 std::min(size_t(1024), mWorkerPrivate->ScriptURL().Length())) 147 : Substring( 148 mWorkerPrivate->WorkerName(), 0, 149 std::min(size_t(1024), mWorkerPrivate->WorkerName().Length()))); 150 AUTO_PROFILER_MARKER_TEXT("Worker.postMessage", DOM, {}, nameOrScriptURL); 151 uint32_t flags = uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS); 152 if (mWorkerPrivate->IsChromeWorker()) { 153 flags |= uint32_t(js::ProfilingStackFrame::Flags::NONSENSITIVE); 154 } 155 mozilla::AutoProfilerLabel PROFILER_RAII( 156 "Worker.postMessage", nameOrScriptURL.get(), 157 JS::ProfilingCategoryPair::DOM, flags); 158 159 RefPtr<MessageEventRunnable> runnable = 160 new MessageEventRunnable(mWorkerPrivate); 161 162 JS::CloneDataPolicy clonePolicy; 163 // DedicatedWorkers are always part of the same agent cluster. 164 clonePolicy.allowIntraClusterClonableSharedObjects(); 165 166 if (NS_IsMainThread()) { 167 nsGlobalWindowInner* win = nsContentUtils::IncumbentInnerWindow(); 168 if (win && win->IsSharedMemoryAllowed()) { 169 clonePolicy.allowSharedMemoryObjects(); 170 } 171 } else { 172 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); 173 if (worker && worker->IsSharedMemoryAllowed()) { 174 clonePolicy.allowSharedMemoryObjects(); 175 } 176 } 177 178 runnable->Write(aCx, aMessage, transferable, clonePolicy, aRv); 179 180 if (!mWorkerPrivate || mWorkerPrivate->ParentStatusProtected() > Running) { 181 return; 182 } 183 184 if (NS_WARN_IF(aRv.Failed())) { 185 return; 186 } 187 188 // The worker could have closed between the time we entered this function and 189 // checked ParentStatusProtected and now, which could cause the dispatch to 190 // fail. 191 (void)NS_WARN_IF(!runnable->Dispatch(mWorkerPrivate)); 192 } 193 194 void Worker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, 195 const StructuredSerializeOptions& aOptions, 196 ErrorResult& aRv) { 197 PostMessage(aCx, aMessage, aOptions.mTransfer, aRv); 198 } 199 200 void Worker::PostEventWithOptions(JSContext* aCx, 201 JS::Handle<JS::Value> aOptions, 202 const Sequence<JSObject*>& aTransferable, 203 EventWithOptionsRunnable* aRunnable, 204 ErrorResult& aRv) { 205 NS_ASSERT_OWNINGTHREAD(Worker); 206 207 if (NS_WARN_IF(!mWorkerPrivate || 208 mWorkerPrivate->ParentStatusProtected() > Running)) { 209 return; 210 } 211 RefPtr<WorkerPrivate> workerPrivate = mWorkerPrivate; 212 (void)workerPrivate; 213 214 aRunnable->InitOptions(aCx, aOptions, aTransferable, aRv); 215 216 if (NS_WARN_IF(!mWorkerPrivate || 217 mWorkerPrivate->ParentStatusProtected() > Running)) { 218 return; 219 } 220 221 if (NS_WARN_IF(aRv.Failed())) { 222 return; 223 } 224 225 (void)NS_WARN_IF(!aRunnable->Dispatch(mWorkerPrivate)); 226 } 227 228 void Worker::Terminate() { 229 NS_ASSERT_OWNINGTHREAD(Worker); 230 231 if (mWorkerPrivate) { 232 mWorkerPrivate->Cancel(); 233 mWorkerPrivate = nullptr; 234 } 235 } 236 237 NS_IMPL_CYCLE_COLLECTION_CLASS(Worker) 238 239 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Worker, DOMEventTargetHelper) 240 if (tmp->mWorkerPrivate) { 241 tmp->mWorkerPrivate->Traverse(cb); 242 } 243 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 244 245 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Worker, DOMEventTargetHelper) 246 tmp->Terminate(); 247 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR 248 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 249 250 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Worker, DOMEventTargetHelper) 251 NS_IMPL_CYCLE_COLLECTION_TRACE_END 252 253 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Worker) 254 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 255 256 NS_IMPL_ADDREF_INHERITED(Worker, DOMEventTargetHelper) 257 NS_IMPL_RELEASE_INHERITED(Worker, DOMEventTargetHelper) 258 259 } // namespace mozilla::dom