diff options
Diffstat (limited to 'dom/broadcastchannel')
35 files changed, 2394 insertions, 0 deletions
diff --git a/dom/broadcastchannel/BroadcastChannel.cpp b/dom/broadcastchannel/BroadcastChannel.cpp new file mode 100644 index 000000000..c3c2d448b --- /dev/null +++ b/dom/broadcastchannel/BroadcastChannel.cpp @@ -0,0 +1,685 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BroadcastChannel.h" +#include "BroadcastChannelChild.h" +#include "mozilla/dom/BroadcastChannelBinding.h" +#include "mozilla/dom/Navigator.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/StructuredCloneHolder.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "nsContentUtils.h" +#include "WorkerPrivate.h" +#include "WorkerRunnable.h" + +#include "nsIBFCacheEntry.h" +#include "nsIDocument.h" +#include "nsISupportsPrimitives.h" + +#ifdef XP_WIN +#undef PostMessage +#endif + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +using namespace workers; + +class BroadcastChannelMessage final : public StructuredCloneHolder +{ +public: + NS_INLINE_DECL_REFCOUNTING(BroadcastChannelMessage) + + BroadcastChannelMessage() + : StructuredCloneHolder(CloningSupported, TransferringNotSupported, + StructuredCloneScope::DifferentProcess) + {} + +private: + ~BroadcastChannelMessage() + {} +}; + +namespace { + +nsIPrincipal* +GetPrincipalFromWorkerPrivate(WorkerPrivate* aWorkerPrivate) +{ + nsIPrincipal* principal = aWorkerPrivate->GetPrincipal(); + if (principal) { + return principal; + } + + // Walk up to our containing page + WorkerPrivate* wp = aWorkerPrivate; + while (wp->GetParent()) { + wp = wp->GetParent(); + } + + return wp->GetPrincipal(); +} + +class InitializeRunnable final : public WorkerMainThreadRunnable +{ +public: + InitializeRunnable(WorkerPrivate* aWorkerPrivate, nsACString& aOrigin, + PrincipalInfo& aPrincipalInfo, ErrorResult& aRv) + : WorkerMainThreadRunnable(aWorkerPrivate, + NS_LITERAL_CSTRING("BroadcastChannel :: Initialize")) + , mWorkerPrivate(GetCurrentThreadWorkerPrivate()) + , mOrigin(aOrigin) + , mPrincipalInfo(aPrincipalInfo) + , mRv(aRv) + { + MOZ_ASSERT(mWorkerPrivate); + } + + bool MainThreadRun() override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsIPrincipal* principal = GetPrincipalFromWorkerPrivate(mWorkerPrivate); + if (!principal) { + mRv.Throw(NS_ERROR_FAILURE); + return true; + } + + if (NS_WARN_IF(principal->GetIsNullPrincipal())) { + mRv.Throw(NS_ERROR_FAILURE); + return true; + } + + mRv = PrincipalToPrincipalInfo(principal, &mPrincipalInfo); + if (NS_WARN_IF(mRv.Failed())) { + return true; + } + + mRv = principal->GetOrigin(mOrigin); + if (NS_WARN_IF(mRv.Failed())) { + return true; + } + + // Walk up to our containing page + WorkerPrivate* wp = mWorkerPrivate; + while (wp->GetParent()) { + wp = wp->GetParent(); + } + + // Window doesn't exist for some kind of workers (eg: SharedWorkers) + nsPIDOMWindowInner* window = wp->GetWindow(); + if (!window) { + return true; + } + + return true; + } + +private: + WorkerPrivate* mWorkerPrivate; + nsACString& mOrigin; + PrincipalInfo& mPrincipalInfo; + ErrorResult& mRv; +}; + +class BCPostMessageRunnable final : public nsIRunnable, + public nsICancelableRunnable +{ +public: + NS_DECL_ISUPPORTS + + BCPostMessageRunnable(BroadcastChannelChild* aActor, + BroadcastChannelMessage* aData) + : mActor(aActor) + , mData(aData) + { + MOZ_ASSERT(mActor); + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(mActor); + if (mActor->IsActorDestroyed()) { + return NS_OK; + } + + ClonedMessageData message; + + bool success; + SerializedStructuredCloneBuffer& buffer = message.data(); + auto iter = mData->BufferData().Iter(); + buffer.data = mData->BufferData().Borrow<js::SystemAllocPolicy>(iter, mData->BufferData().Size(), &success); + if (NS_WARN_IF(!success)) { + return NS_OK; + } + + PBackgroundChild* backgroundManager = mActor->Manager(); + MOZ_ASSERT(backgroundManager); + + const nsTArray<RefPtr<BlobImpl>>& blobImpls = mData->BlobImpls(); + + if (!blobImpls.IsEmpty()) { + message.blobsChild().SetCapacity(blobImpls.Length()); + + for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) { + PBlobChild* blobChild = + BackgroundChild::GetOrCreateActorForBlobImpl(backgroundManager, + blobImpls[i]); + MOZ_ASSERT(blobChild); + + message.blobsChild().AppendElement(blobChild); + } + } + + mActor->SendPostMessage(message); + return NS_OK; + } + + nsresult Cancel() override + { + mActor = nullptr; + return NS_OK; + } + +private: + ~BCPostMessageRunnable() {} + + RefPtr<BroadcastChannelChild> mActor; + RefPtr<BroadcastChannelMessage> mData; +}; + +NS_IMPL_ISUPPORTS(BCPostMessageRunnable, nsICancelableRunnable, nsIRunnable) + +class CloseRunnable final : public nsIRunnable, + public nsICancelableRunnable +{ +public: + NS_DECL_ISUPPORTS + + explicit CloseRunnable(BroadcastChannel* aBC) + : mBC(aBC) + { + MOZ_ASSERT(mBC); + } + + NS_IMETHOD Run() override + { + mBC->Shutdown(); + return NS_OK; + } + + nsresult Cancel() override + { + mBC = nullptr; + return NS_OK; + } + +private: + ~CloseRunnable() {} + + RefPtr<BroadcastChannel> mBC; +}; + +NS_IMPL_ISUPPORTS(CloseRunnable, nsICancelableRunnable, nsIRunnable) + +class TeardownRunnable final : public nsIRunnable, + public nsICancelableRunnable +{ +public: + NS_DECL_ISUPPORTS + + explicit TeardownRunnable(BroadcastChannelChild* aActor) + : mActor(aActor) + { + MOZ_ASSERT(mActor); + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(mActor); + if (!mActor->IsActorDestroyed()) { + mActor->SendClose(); + } + return NS_OK; + } + + nsresult Cancel() override + { + mActor = nullptr; + return NS_OK; + } + +private: + ~TeardownRunnable() {} + + RefPtr<BroadcastChannelChild> mActor; +}; + +NS_IMPL_ISUPPORTS(TeardownRunnable, nsICancelableRunnable, nsIRunnable) + +class BroadcastChannelWorkerHolder final : public workers::WorkerHolder +{ + BroadcastChannel* mChannel; + +public: + explicit BroadcastChannelWorkerHolder(BroadcastChannel* aChannel) + : mChannel(aChannel) + { + MOZ_COUNT_CTOR(BroadcastChannelWorkerHolder); + } + + virtual bool Notify(workers::Status aStatus) override + { + if (aStatus >= Closing) { + mChannel->Shutdown(); + } + + return true; + } + +private: + ~BroadcastChannelWorkerHolder() + { + MOZ_COUNT_DTOR(BroadcastChannelWorkerHolder); + } +}; + +} // namespace + +BroadcastChannel::BroadcastChannel(nsPIDOMWindowInner* aWindow, + const PrincipalInfo& aPrincipalInfo, + const nsACString& aOrigin, + const nsAString& aChannel) + : DOMEventTargetHelper(aWindow) + , mWorkerHolder(nullptr) + , mPrincipalInfo(new PrincipalInfo(aPrincipalInfo)) + , mOrigin(aOrigin) + , mChannel(aChannel) + , mIsKeptAlive(false) + , mInnerID(0) + , mState(StateActive) +{ + // Window can be null in workers +} + +BroadcastChannel::~BroadcastChannel() +{ + Shutdown(); + MOZ_ASSERT(!mWorkerHolder); +} + +JSObject* +BroadcastChannel::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return BroadcastChannelBinding::Wrap(aCx, this, aGivenProto); +} + +/* static */ already_AddRefed<BroadcastChannel> +BroadcastChannel::Constructor(const GlobalObject& aGlobal, + const nsAString& aChannel, + ErrorResult& aRv) +{ + nsCOMPtr<nsPIDOMWindowInner> window = + do_QueryInterface(aGlobal.GetAsSupports()); + // Window is null in workers. + + nsAutoCString origin; + PrincipalInfo principalInfo; + WorkerPrivate* workerPrivate = nullptr; + + if (NS_IsMainThread()) { + nsCOMPtr<nsIGlobalObject> incumbent = mozilla::dom::GetIncumbentGlobal(); + + if (!incumbent) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsIPrincipal* principal = incumbent->PrincipalOrNull(); + if (!principal) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + if (NS_WARN_IF(principal->GetIsNullPrincipal())) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + aRv = principal->GetOrigin(origin); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + aRv = PrincipalToPrincipalInfo(principal, &principalInfo); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + } else { + JSContext* cx = aGlobal.Context(); + workerPrivate = GetWorkerPrivateFromContext(cx); + MOZ_ASSERT(workerPrivate); + + RefPtr<InitializeRunnable> runnable = + new InitializeRunnable(workerPrivate, origin, principalInfo, aRv); + runnable->Dispatch(aRv); + } + + if (aRv.Failed()) { + return nullptr; + } + + RefPtr<BroadcastChannel> bc = + new BroadcastChannel(window, principalInfo, origin, aChannel); + + // Register this component to PBackground. + PBackgroundChild* actor = BackgroundChild::GetForCurrentThread(); + if (actor) { + bc->ActorCreated(actor); + } else { + BackgroundChild::GetOrCreateForCurrentThread(bc); + } + + if (!workerPrivate) { + MOZ_ASSERT(window); + MOZ_ASSERT(window->IsInnerWindow()); + bc->mInnerID = window->WindowID(); + + // Register as observer for inner-window-destroyed. + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(bc, "inner-window-destroyed", false); + } + } else { + bc->mWorkerHolder = new BroadcastChannelWorkerHolder(bc); + if (NS_WARN_IF(!bc->mWorkerHolder->HoldWorker(workerPrivate, Closing))) { + bc->mWorkerHolder = nullptr; + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + } + + return bc.forget(); +} + +void +BroadcastChannel::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, + ErrorResult& aRv) +{ + if (mState != StateActive) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + PostMessageInternal(aCx, aMessage, aRv); +} + +void +BroadcastChannel::PostMessageInternal(JSContext* aCx, + JS::Handle<JS::Value> aMessage, + ErrorResult& aRv) +{ + RefPtr<BroadcastChannelMessage> data = new BroadcastChannelMessage(); + + data->Write(aCx, aMessage, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + PostMessageData(data); +} + +void +BroadcastChannel::PostMessageData(BroadcastChannelMessage* aData) +{ + RemoveDocFromBFCache(); + + if (mActor) { + RefPtr<BCPostMessageRunnable> runnable = + new BCPostMessageRunnable(mActor, aData); + + if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { + NS_WARNING("Failed to dispatch to the current thread!"); + } + + return; + } + + mPendingMessages.AppendElement(aData); +} + +void +BroadcastChannel::Close() +{ + if (mState != StateActive) { + return; + } + + if (mPendingMessages.IsEmpty()) { + // We cannot call Shutdown() immediatelly because we could have some + // postMessage runnable already dispatched. Instead, we change the state to + // StateClosed and we shutdown the actor asynchrounsly. + + mState = StateClosed; + RefPtr<CloseRunnable> runnable = new CloseRunnable(this); + + if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { + NS_WARNING("Failed to dispatch to the current thread!"); + } + } else { + MOZ_ASSERT(!mActor); + mState = StateClosing; + } +} + +void +BroadcastChannel::ActorFailed() +{ + MOZ_CRASH("Failed to create a PBackgroundChild actor!"); +} + +void +BroadcastChannel::ActorCreated(PBackgroundChild* aActor) +{ + MOZ_ASSERT(aActor); + + if (mState == StateClosed) { + return; + } + + PBroadcastChannelChild* actor = + aActor->SendPBroadcastChannelConstructor(*mPrincipalInfo, mOrigin, mChannel); + + mActor = static_cast<BroadcastChannelChild*>(actor); + MOZ_ASSERT(mActor); + + mActor->SetParent(this); + + // Flush pending messages. + for (uint32_t i = 0; i < mPendingMessages.Length(); ++i) { + PostMessageData(mPendingMessages[i]); + } + + mPendingMessages.Clear(); + + if (mState == StateClosing) { + Shutdown(); + } +} + +void +BroadcastChannel::Shutdown() +{ + mState = StateClosed; + + // The DTOR of this WorkerHolder will release the worker for us. + mWorkerHolder = nullptr; + + if (mActor) { + mActor->SetParent(nullptr); + + RefPtr<TeardownRunnable> runnable = new TeardownRunnable(mActor); + NS_DispatchToCurrentThread(runnable); + + mActor = nullptr; + } + + // If shutdown() is called we have to release the reference if we still keep + // it. + if (mIsKeptAlive) { + mIsKeptAlive = false; + Release(); + } +} + +EventHandlerNonNull* +BroadcastChannel::GetOnmessage() +{ + if (NS_IsMainThread()) { + return GetEventHandler(nsGkAtoms::onmessage, EmptyString()); + } + return GetEventHandler(nullptr, NS_LITERAL_STRING("message")); +} + +void +BroadcastChannel::SetOnmessage(EventHandlerNonNull* aCallback) +{ + if (NS_IsMainThread()) { + SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback); + } else { + SetEventHandler(nullptr, NS_LITERAL_STRING("message"), aCallback); + } + + UpdateMustKeepAlive(); +} + +void +BroadcastChannel::AddEventListener(const nsAString& aType, + EventListener* aCallback, + const AddEventListenerOptionsOrBoolean& aOptions, + const dom::Nullable<bool>& aWantsUntrusted, + ErrorResult& aRv) +{ + DOMEventTargetHelper::AddEventListener(aType, aCallback, aOptions, + aWantsUntrusted, aRv); + + if (aRv.Failed()) { + return; + } + + UpdateMustKeepAlive(); +} + +void +BroadcastChannel::RemoveEventListener(const nsAString& aType, + EventListener* aCallback, + const EventListenerOptionsOrBoolean& aOptions, + ErrorResult& aRv) +{ + DOMEventTargetHelper::RemoveEventListener(aType, aCallback, aOptions, aRv); + + if (aRv.Failed()) { + return; + } + + UpdateMustKeepAlive(); +} + +void +BroadcastChannel::UpdateMustKeepAlive() +{ + bool toKeepAlive = HasListenersFor(NS_LITERAL_STRING("message")); + if (toKeepAlive == mIsKeptAlive) { + return; + } + + mIsKeptAlive = toKeepAlive; + + if (toKeepAlive) { + AddRef(); + } else { + Release(); + } +} + +NS_IMETHODIMP +BroadcastChannel::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!strcmp(aTopic, "inner-window-destroyed")); + + // If the window is destroyed we have to release the reference that we are + // keeping. + nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject); + NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE); + + uint64_t innerID; + nsresult rv = wrapper->GetData(&innerID); + NS_ENSURE_SUCCESS(rv, rv); + + if (innerID == mInnerID) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "inner-window-destroyed"); + } + + Shutdown(); + } + + return NS_OK; +} + +void +BroadcastChannel::RemoveDocFromBFCache() +{ + if (!NS_IsMainThread()) { + return; + } + + nsPIDOMWindowInner* window = GetOwner(); + if (!window) { + return; + } + + nsIDocument* doc = window->GetExtantDoc(); + if (!doc) { + return; + } + + nsCOMPtr<nsIBFCacheEntry> bfCacheEntry = doc->GetBFCacheEntry(); + if (!bfCacheEntry) { + return; + } + + bfCacheEntry->RemoveFromBFCacheSync(); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(BroadcastChannel) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BroadcastChannel, + DOMEventTargetHelper) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BroadcastChannel, + DOMEventTargetHelper) + tmp->Shutdown(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BroadcastChannel) + NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback) + NS_INTERFACE_MAP_ENTRY(nsIObserver) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(BroadcastChannel, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(BroadcastChannel, DOMEventTargetHelper) + +} // namespace dom +} // namespace mozilla diff --git a/dom/broadcastchannel/BroadcastChannel.h b/dom/broadcastchannel/BroadcastChannel.h new file mode 100644 index 000000000..d7d46eeec --- /dev/null +++ b/dom/broadcastchannel/BroadcastChannel.h @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BroadcastChannel_h +#define mozilla_dom_BroadcastChannel_h + +#include "mozilla/Attributes.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "nsAutoPtr.h" +#include "nsIIPCBackgroundChildCreateCallback.h" +#include "nsIObserver.h" +#include "nsTArray.h" +#include "mozilla/RefPtr.h" + +class nsPIDOMWindowInner; + +namespace mozilla { + +namespace ipc { +class PrincipalInfo; +} // namespace ipc + +namespace dom { + +namespace workers { +class WorkerHolder; +} // namespace workers + +class BroadcastChannelChild; +class BroadcastChannelMessage; + +class BroadcastChannel final + : public DOMEventTargetHelper + , public nsIIPCBackgroundChildCreateCallback + , public nsIObserver +{ + friend class BroadcastChannelChild; + + NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK + NS_DECL_NSIOBSERVER + + typedef mozilla::ipc::PrincipalInfo PrincipalInfo; + +public: + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BroadcastChannel, + DOMEventTargetHelper) + + virtual JSObject* + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + static already_AddRefed<BroadcastChannel> + Constructor(const GlobalObject& aGlobal, const nsAString& aChannel, + ErrorResult& aRv); + + void GetName(nsAString& aName) const + { + aName = mChannel; + } + + void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, + ErrorResult& aRv); + + void Close(); + + EventHandlerNonNull* GetOnmessage(); + void SetOnmessage(EventHandlerNonNull* aCallback); + + using nsIDOMEventTarget::AddEventListener; + using nsIDOMEventTarget::RemoveEventListener; + + virtual void AddEventListener(const nsAString& aType, + EventListener* aCallback, + const AddEventListenerOptionsOrBoolean& aOptions, + const Nullable<bool>& aWantsUntrusted, + ErrorResult& aRv) override; + virtual void RemoveEventListener(const nsAString& aType, + EventListener* aCallback, + const EventListenerOptionsOrBoolean& aOptions, + ErrorResult& aRv) override; + + void Shutdown(); + +private: + BroadcastChannel(nsPIDOMWindowInner* aWindow, + const PrincipalInfo& aPrincipalInfo, + const nsACString& aOrigin, + const nsAString& aChannel); + + ~BroadcastChannel(); + + void PostMessageData(BroadcastChannelMessage* aData); + + void PostMessageInternal(JSContext* aCx, JS::Handle<JS::Value> aMessage, + ErrorResult& aRv); + + void UpdateMustKeepAlive(); + + bool IsCertainlyAliveForCC() const override + { + return mIsKeptAlive; + } + + void RemoveDocFromBFCache(); + + RefPtr<BroadcastChannelChild> mActor; + nsTArray<RefPtr<BroadcastChannelMessage>> mPendingMessages; + + nsAutoPtr<workers::WorkerHolder> mWorkerHolder; + + nsAutoPtr<PrincipalInfo> mPrincipalInfo; + + nsCString mOrigin; + nsString mChannel; + + bool mIsKeptAlive; + + uint64_t mInnerID; + + enum { + StateActive, + StateClosing, + StateClosed + } mState; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_BroadcastChannel_h diff --git a/dom/broadcastchannel/BroadcastChannelChild.cpp b/dom/broadcastchannel/BroadcastChannelChild.cpp new file mode 100644 index 000000000..98bd21999 --- /dev/null +++ b/dom/broadcastchannel/BroadcastChannelChild.cpp @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BroadcastChannelChild.h" +#include "BroadcastChannel.h" +#include "jsapi.h" +#include "mozilla/dom/ipc/BlobChild.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/MessageEvent.h" +#include "mozilla/dom/MessageEventBinding.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerScope.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "WorkerPrivate.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +using namespace workers; + +BroadcastChannelChild::BroadcastChannelChild(const nsACString& aOrigin) + : mBC(nullptr) + , mActorDestroyed(false) +{ + CopyUTF8toUTF16(aOrigin, mOrigin); +} + +BroadcastChannelChild::~BroadcastChannelChild() +{ + MOZ_ASSERT(!mBC); +} + +bool +BroadcastChannelChild::RecvNotify(const ClonedMessageData& aData) +{ + // Make sure to retrieve all blobs from the message before returning to avoid + // leaking their actors. + nsTArray<RefPtr<BlobImpl>> blobs; + if (!aData.blobsChild().IsEmpty()) { + blobs.SetCapacity(aData.blobsChild().Length()); + + for (uint32_t i = 0, len = aData.blobsChild().Length(); i < len; ++i) { + RefPtr<BlobImpl> impl = + static_cast<BlobChild*>(aData.blobsChild()[i])->GetBlobImpl(); + + blobs.AppendElement(impl); + } + } + + nsCOMPtr<DOMEventTargetHelper> helper = mBC; + nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(helper); + + // The object is going to be deleted soon. No notify is required. + if (!eventTarget) { + return true; + } + + // CheckInnerWindowCorrectness can be used also without a window when + // BroadcastChannel is running in a worker. In this case, it's a NOP. + if (NS_FAILED(mBC->CheckInnerWindowCorrectness())) { + return true; + } + + mBC->RemoveDocFromBFCache(); + + AutoJSAPI jsapi; + nsCOMPtr<nsIGlobalObject> globalObject; + + if (NS_IsMainThread()) { + globalObject = do_QueryInterface(mBC->GetParentObject()); + } else { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + globalObject = workerPrivate->GlobalScope(); + } + + if (!globalObject || !jsapi.Init(globalObject)) { + NS_WARNING("Failed to initialize AutoJSAPI object."); + return true; + } + + ipc::StructuredCloneData cloneData; + cloneData.BlobImpls().AppendElements(blobs); + + const SerializedStructuredCloneBuffer& buffer = aData.data(); + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> value(cx, JS::NullValue()); + if (buffer.data.Size()) { + ErrorResult rv; + cloneData.UseExternalData(buffer.data); + cloneData.Read(cx, &value, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return true; + } + } + + RootedDictionary<MessageEventInit> init(cx); + init.mBubbles = false; + init.mCancelable = false; + init.mOrigin = mOrigin; + init.mData = value; + + ErrorResult rv; + RefPtr<MessageEvent> event = + MessageEvent::Constructor(mBC, NS_LITERAL_STRING("message"), init, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return true; + } + + event->SetTrusted(true); + + bool status; + mBC->DispatchEvent(static_cast<Event*>(event.get()), &status); + + return true; +} + +void +BroadcastChannelChild::ActorDestroy(ActorDestroyReason aWhy) +{ + mActorDestroyed = true; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/broadcastchannel/BroadcastChannelChild.h b/dom/broadcastchannel/BroadcastChannelChild.h new file mode 100644 index 000000000..23cef899e --- /dev/null +++ b/dom/broadcastchannel/BroadcastChannelChild.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BroadcastChannelChild_h +#define mozilla_dom_BroadcastChannelChild_h + +#include "mozilla/dom/PBroadcastChannelChild.h" + +namespace mozilla { + +namespace ipc { +class BackgroundChildImpl; +} // namespace ipc + +namespace dom { + +class BroadcastChannel; + +class BroadcastChannelChild final : public PBroadcastChannelChild +{ + friend class mozilla::ipc::BackgroundChildImpl; + +public: + NS_INLINE_DECL_REFCOUNTING(BroadcastChannelChild) + + void SetParent(BroadcastChannel* aBC) + { + mBC = aBC; + } + + virtual bool RecvNotify(const ClonedMessageData& aData) override; + + bool IsActorDestroyed() const + { + return mActorDestroyed; + } + +private: + explicit BroadcastChannelChild(const nsACString& aOrigin); + ~BroadcastChannelChild(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + // This raw pointer is actually the parent object. + // It's set to null when the parent object is deleted. + BroadcastChannel* mBC; + + nsString mOrigin; + + bool mActorDestroyed; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_BroadcastChannelChild_h diff --git a/dom/broadcastchannel/BroadcastChannelParent.cpp b/dom/broadcastchannel/BroadcastChannelParent.cpp new file mode 100644 index 000000000..f26292e1d --- /dev/null +++ b/dom/broadcastchannel/BroadcastChannelParent.cpp @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BroadcastChannelParent.h" +#include "BroadcastChannelService.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/ipc/BlobParent.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/Unused.h" +#include "nsIScriptSecurityManager.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +BroadcastChannelParent::BroadcastChannelParent(const nsAString& aOriginChannelKey) + : mService(BroadcastChannelService::GetOrCreate()) + , mOriginChannelKey(aOriginChannelKey) +{ + AssertIsOnBackgroundThread(); + mService->RegisterActor(this, mOriginChannelKey); +} + +BroadcastChannelParent::~BroadcastChannelParent() +{ + AssertIsOnBackgroundThread(); +} + +bool +BroadcastChannelParent::RecvPostMessage(const ClonedMessageData& aData) +{ + AssertIsOnBackgroundThread(); + + if (NS_WARN_IF(!mService)) { + return false; + } + + mService->PostMessage(this, aData, mOriginChannelKey); + return true; +} + +bool +BroadcastChannelParent::RecvClose() +{ + AssertIsOnBackgroundThread(); + + if (NS_WARN_IF(!mService)) { + return false; + } + + mService->UnregisterActor(this, mOriginChannelKey); + mService = nullptr; + + Unused << Send__delete__(this); + + return true; +} + +void +BroadcastChannelParent::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnBackgroundThread(); + + if (mService) { + // This object is about to be released and with it, also mService will be + // released too. + mService->UnregisterActor(this, mOriginChannelKey); + } +} + +void +BroadcastChannelParent::Deliver(const ClonedMessageData& aData) +{ + AssertIsOnBackgroundThread(); + + // Duplicate the data for this parent. + ClonedMessageData newData(aData); + + // Create new BlobParent objects for this message. + for (uint32_t i = 0, len = newData.blobsParent().Length(); i < len; ++i) { + RefPtr<BlobImpl> impl = + static_cast<BlobParent*>(newData.blobsParent()[i])->GetBlobImpl(); + + PBlobParent* blobParent = + BackgroundParent::GetOrCreateActorForBlobImpl(Manager(), impl); + if (!blobParent) { + return; + } + + newData.blobsParent()[i] = blobParent; + } + + Unused << SendNotify(newData); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/broadcastchannel/BroadcastChannelParent.h b/dom/broadcastchannel/BroadcastChannelParent.h new file mode 100644 index 000000000..e71354ca7 --- /dev/null +++ b/dom/broadcastchannel/BroadcastChannelParent.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BroadcastChannelParent_h +#define mozilla_dom_BroadcastChannelParent_h + +#include "mozilla/dom/PBroadcastChannelParent.h" + +namespace mozilla { + +namespace ipc { +class BackgroundParentImpl; +class PrincipalInfo; +} // namespace ipc + +namespace dom { + +class BroadcastChannelService; + +class BroadcastChannelParent final : public PBroadcastChannelParent +{ + friend class mozilla::ipc::BackgroundParentImpl; + + typedef mozilla::ipc::PrincipalInfo PrincipalInfo; + +public: + void Deliver(const ClonedMessageData& aData); + +private: + explicit BroadcastChannelParent(const nsAString& aOriginChannelKey); + ~BroadcastChannelParent(); + + virtual bool + RecvPostMessage(const ClonedMessageData& aData) override; + + virtual bool RecvClose() override; + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + RefPtr<BroadcastChannelService> mService; + const nsString mOriginChannelKey; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_BroadcastChannelParent_h diff --git a/dom/broadcastchannel/BroadcastChannelService.cpp b/dom/broadcastchannel/BroadcastChannelService.cpp new file mode 100644 index 000000000..f88108c3a --- /dev/null +++ b/dom/broadcastchannel/BroadcastChannelService.cpp @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BroadcastChannelService.h" +#include "BroadcastChannelParent.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/ipc/BlobParent.h" +#include "mozilla/ipc/BackgroundParent.h" + +#ifdef XP_WIN +#undef PostMessage +#endif + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +namespace { + +BroadcastChannelService* sInstance = nullptr; + +} // namespace + +BroadcastChannelService::BroadcastChannelService() +{ + AssertIsOnBackgroundThread(); + + // sInstance is a raw BroadcastChannelService*. + MOZ_ASSERT(!sInstance); + sInstance = this; +} + +BroadcastChannelService::~BroadcastChannelService() +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(sInstance == this); + MOZ_ASSERT(mAgents.Count() == 0); + + sInstance = nullptr; +} + +// static +already_AddRefed<BroadcastChannelService> +BroadcastChannelService::GetOrCreate() +{ + AssertIsOnBackgroundThread(); + + RefPtr<BroadcastChannelService> instance = sInstance; + if (!instance) { + instance = new BroadcastChannelService(); + } + return instance.forget(); +} + +void +BroadcastChannelService::RegisterActor(BroadcastChannelParent* aParent, + const nsAString& aOriginChannelKey) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aParent); + + nsTArray<BroadcastChannelParent*>* parents; + if (!mAgents.Get(aOriginChannelKey, &parents)) { + parents = new nsTArray<BroadcastChannelParent*>(); + mAgents.Put(aOriginChannelKey, parents); + } + + MOZ_ASSERT(!parents->Contains(aParent)); + parents->AppendElement(aParent); +} + +void +BroadcastChannelService::UnregisterActor(BroadcastChannelParent* aParent, + const nsAString& aOriginChannelKey) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aParent); + + nsTArray<BroadcastChannelParent*>* parents; + if (!mAgents.Get(aOriginChannelKey, &parents)) { + MOZ_CRASH("Invalid state"); + } + + parents->RemoveElement(aParent); + if (parents->IsEmpty()) { + mAgents.Remove(aOriginChannelKey); + } +} + +void +BroadcastChannelService::PostMessage(BroadcastChannelParent* aParent, + const ClonedMessageData& aData, + const nsAString& aOriginChannelKey) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aParent); + + nsTArray<BroadcastChannelParent*>* parents; + if (!mAgents.Get(aOriginChannelKey, &parents)) { + MOZ_CRASH("Invalid state"); + } + + // We need to keep the array alive for the life-time of this operation. + nsTArray<RefPtr<BlobImpl>> blobs; + if (!aData.blobsParent().IsEmpty()) { + blobs.SetCapacity(aData.blobsParent().Length()); + + for (uint32_t i = 0, len = aData.blobsParent().Length(); i < len; ++i) { + RefPtr<BlobImpl> impl = + static_cast<BlobParent*>(aData.blobsParent()[i])->GetBlobImpl(); + MOZ_ASSERT(impl); + blobs.AppendElement(impl); + } + } + + for (uint32_t i = 0; i < parents->Length(); ++i) { + BroadcastChannelParent* parent = parents->ElementAt(i); + MOZ_ASSERT(parent); + + if (parent != aParent) { + parent->Deliver(aData); + } + } +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/broadcastchannel/BroadcastChannelService.h b/dom/broadcastchannel/BroadcastChannelService.h new file mode 100644 index 000000000..3934a7ebf --- /dev/null +++ b/dom/broadcastchannel/BroadcastChannelService.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BroadcastChannelService_h +#define mozilla_dom_BroadcastChannelService_h + +#include "nsISupportsImpl.h" +#include "nsHashKeys.h" +#include "nsClassHashtable.h" + +#ifdef XP_WIN +#undef PostMessage +#endif + +namespace mozilla { +namespace dom { + +class BroadcastChannelParent; +class ClonedMessageData; + +class BroadcastChannelService final +{ +public: + NS_INLINE_DECL_REFCOUNTING(BroadcastChannelService) + + static already_AddRefed<BroadcastChannelService> GetOrCreate(); + + void RegisterActor(BroadcastChannelParent* aParent, + const nsAString& aOriginChannelKey); + void UnregisterActor(BroadcastChannelParent* aParent, + const nsAString& aOriginChannelKey); + + void PostMessage(BroadcastChannelParent* aParent, + const ClonedMessageData& aData, + const nsAString& aOriginChannelKey); + +private: + BroadcastChannelService(); + ~BroadcastChannelService(); + + // Raw Pointers because the actors keep alive this service. + nsClassHashtable<nsStringHashKey, + nsTArray<BroadcastChannelParent*>> mAgents; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_BroadcastChannelService_h diff --git a/dom/broadcastchannel/PBroadcastChannel.ipdl b/dom/broadcastchannel/PBroadcastChannel.ipdl new file mode 100644 index 000000000..4b4a53106 --- /dev/null +++ b/dom/broadcastchannel/PBroadcastChannel.ipdl @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PBackground; +include protocol PBlob; +include DOMTypes; + +using struct mozilla::SerializedStructuredCloneBuffer from "ipc/IPCMessageUtils.h"; + +namespace mozilla { +namespace dom { + +// This protocol is used for the BroadcastChannel API +protocol PBroadcastChannel +{ + manager PBackground; + +parent: + async PostMessage(ClonedMessageData message); + async Close(); + +child: + async Notify(ClonedMessageData message); + async __delete__(); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/broadcastchannel/moz.build b/dom/broadcastchannel/moz.build new file mode 100644 index 000000000..a076331e5 --- /dev/null +++ b/dom/broadcastchannel/moz.build @@ -0,0 +1,31 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS.mozilla.dom += [ + 'BroadcastChannel.h', +] + +UNIFIED_SOURCES += [ + 'BroadcastChannel.cpp', + 'BroadcastChannelChild.cpp', + 'BroadcastChannelParent.cpp', + 'BroadcastChannelService.cpp', +] + +IPDL_SOURCES += [ + 'PBroadcastChannel.ipdl', +] + +LOCAL_INCLUDES += [ + '../workers', +] + +MOCHITEST_MANIFESTS += ['tests/mochitest.ini'] +BROWSER_CHROME_MANIFESTS += ['tests/browser.ini'] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' diff --git a/dom/broadcastchannel/tests/blank.html b/dom/broadcastchannel/tests/blank.html new file mode 100644 index 000000000..358db717d --- /dev/null +++ b/dom/broadcastchannel/tests/blank.html @@ -0,0 +1,2 @@ +<!DOCTYPE HTML> +<html><body></body></html> diff --git a/dom/broadcastchannel/tests/broadcastchannel_sharedWorker.js b/dom/broadcastchannel/tests/broadcastchannel_sharedWorker.js new file mode 100644 index 000000000..4dad5ae82 --- /dev/null +++ b/dom/broadcastchannel/tests/broadcastchannel_sharedWorker.js @@ -0,0 +1,12 @@ +onconnect = function(evt) { + evt.ports[0].onmessage = function(evt) { + var bc = new BroadcastChannel('foobar'); + bc.addEventListener('message', function(event) { + bc.postMessage(event.data == "hello world from the window" ? + "hello world from the worker" : "KO"); + bc.close(); + }, false); + + evt.target.postMessage("READY"); + } +} diff --git a/dom/broadcastchannel/tests/broadcastchannel_worker.js b/dom/broadcastchannel/tests/broadcastchannel_worker.js new file mode 100644 index 000000000..4714d59d0 --- /dev/null +++ b/dom/broadcastchannel/tests/broadcastchannel_worker.js @@ -0,0 +1,18 @@ +onmessage = function(evt) { + if (evt.data != 0) { + var worker = new Worker("broadcastchannel_worker.js"); + worker.onmessage = function(evt) { + postMessage(evt.data); + } + worker.postMessage(evt.data - 1); + return; + } + + var bc = new BroadcastChannel('foobar'); + bc.addEventListener('message', function(event) { + bc.postMessage(event.data == "hello world from the window" ? "hello world from the worker" : "KO"); + bc.close(); + }, false); + + postMessage("READY"); +} diff --git a/dom/broadcastchannel/tests/broadcastchannel_worker_alive.js b/dom/broadcastchannel/tests/broadcastchannel_worker_alive.js new file mode 100644 index 000000000..78ab44015 --- /dev/null +++ b/dom/broadcastchannel/tests/broadcastchannel_worker_alive.js @@ -0,0 +1,8 @@ +(new BroadcastChannel('foobar')).postMessage('READY'); + +(new BroadcastChannel('foobar')).addEventListener('message', function(event) { + if (event.data != 'READY') { + event.target.postMessage(event.data); + } +}, false); + diff --git a/dom/broadcastchannel/tests/broadcastchannel_worker_any.js b/dom/broadcastchannel/tests/broadcastchannel_worker_any.js new file mode 100644 index 000000000..da8625c4d --- /dev/null +++ b/dom/broadcastchannel/tests/broadcastchannel_worker_any.js @@ -0,0 +1,5 @@ +(new BroadcastChannel('foobar')).onmessage = function(event) { + event.target.postMessage(event.data); +} + +postMessage("READY"); diff --git a/dom/broadcastchannel/tests/browser.ini b/dom/broadcastchannel/tests/browser.ini new file mode 100644 index 000000000..b036a1e9b --- /dev/null +++ b/dom/broadcastchannel/tests/browser.ini @@ -0,0 +1,6 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + blank.html + +[browser_private_browsing.js] diff --git a/dom/broadcastchannel/tests/browser_private_browsing.js b/dom/broadcastchannel/tests/browser_private_browsing.js new file mode 100644 index 000000000..5724225fc --- /dev/null +++ b/dom/broadcastchannel/tests/browser_private_browsing.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const URL = "http://mochi.test:8888/browser/dom/broadcastchannel/tests/blank.html"; + +add_task(function*() { + var win1 = OpenBrowserWindow({private: true}); + var win1Promise = new win1.Promise(resolve => { + win1.addEventListener("load", function onLoad() { + win1.removeEventListener("load", onLoad, false); + resolve(); + }); + }); + yield win1Promise; + + var win2 = OpenBrowserWindow({private: false}); + var win2Promise = new win2.Promise(resolve => { + win2.addEventListener("load", function onLoad() { + win2.removeEventListener("load", onLoad, false); + resolve(); + }); + }); + yield win2Promise; + + var tab1 = win1.gBrowser.addTab(URL); + yield BrowserTestUtils.browserLoaded(win1.gBrowser.getBrowserForTab(tab1)); + var browser1 = gBrowser.getBrowserForTab(tab1); + + var tab2 = win2.gBrowser.addTab(URL); + yield BrowserTestUtils.browserLoaded(win2.gBrowser.getBrowserForTab(tab2)); + var browser2 = gBrowser.getBrowserForTab(tab2); + + var p1 = ContentTask.spawn(browser1, null, function(opts) { + return new content.window.Promise(resolve => { + content.window.bc = new content.window.BroadcastChannel('foobar'); + content.window.bc.onmessage = function(e) { resolve(e.data); } + }); + }); + + var p2 = ContentTask.spawn(browser2, null, function(opts) { + return new content.window.Promise(resolve => { + content.window.bc = new content.window.BroadcastChannel('foobar'); + content.window.bc.onmessage = function(e) { resolve(e.data); } + }); + }); + + yield ContentTask.spawn(browser1, null, function(opts) { + return new content.window.Promise(resolve => { + var bc = new content.window.BroadcastChannel('foobar'); + bc.postMessage('hello world from private browsing'); + resolve(); + }); + }); + + yield ContentTask.spawn(browser2, null, function(opts) { + return new content.window.Promise(resolve => { + var bc = new content.window.BroadcastChannel('foobar'); + bc.postMessage('hello world from non private browsing'); + resolve(); + }); + }); + + var what1 = yield p1; + ok(what1, 'hello world from private browsing', 'No messages received from the other window.'); + + var what2 = yield p2; + ok(what1, 'hello world from non private browsing', 'No messages received from the other window.'); + + yield BrowserTestUtils.removeTab(tab1); + yield BrowserTestUtils.closeWindow(win1); + + yield BrowserTestUtils.removeTab(tab2); + yield BrowserTestUtils.closeWindow(win2); +}); diff --git a/dom/broadcastchannel/tests/file_mozbrowser.html b/dom/broadcastchannel/tests/file_mozbrowser.html new file mode 100644 index 000000000..5f6902132 --- /dev/null +++ b/dom/broadcastchannel/tests/file_mozbrowser.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>MozBrowser iframe</title> +</head> +<body> +<div id="container"></div> + <script type="application/javascript;version=1.7"> + + var ifr = document.createElement('iframe'); + ifr.src = 'http://mochi.test:8888/tests/dom/broadcastchannel/tests/iframe_mozbrowser.html'; + ifr.onload = function() { alert('DONE'); } + + var domParent = document.getElementById('container'); + domParent.appendChild(ifr); + + </script> +</body> +</html> diff --git a/dom/broadcastchannel/tests/file_mozbrowser2.html b/dom/broadcastchannel/tests/file_mozbrowser2.html new file mode 100644 index 000000000..85abce7bf --- /dev/null +++ b/dom/broadcastchannel/tests/file_mozbrowser2.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>MozBrowser iframe</title> +</head> +<body> +<div id="container"></div> + <script type="application/javascript;version=1.7"> + + var ifr = document.createElement('iframe'); + ifr.setAttribute('mozbrowser', true); + ifr.src = 'http://mochi.test:8888/tests/dom/broadcastchannel/tests/iframe_mozbrowser2.html'; + ifr.onload = function() { alert('DONE'); } + + var domParent = document.getElementById('container'); + domParent.appendChild(ifr); + + </script> +</body> +</html> diff --git a/dom/broadcastchannel/tests/iframe_broadcastchannel.html b/dom/broadcastchannel/tests/iframe_broadcastchannel.html new file mode 100644 index 000000000..fb9a12b4b --- /dev/null +++ b/dom/broadcastchannel/tests/iframe_broadcastchannel.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<body> + <script type="application/javascript"> + +function is(a, b, msg) { + ok(a == b, msg); +} + +function ok(a, msg) { + window.parent.postMessage({ status: a ? "OK" : "KO", message: msg }, "*"); +} + +ok("BroadcastChannel" in window, "BroadcastChannel exists"); + +var bc = new BroadcastChannel("foobar"); +ok(bc, "BroadcastChannel can be created"); +is(bc.name, 'foobar', "BroadcastChannel.name is foobar"); + +ok("postMessage" in bc, "BroadcastChannel has postMessage() method"); + +bc.onmessage = function(evt) { + ok(evt instanceof MessageEvent, 'evt is a MessageEvent'); + is(evt.target, bc, 'MessageEvent.target is bc'); + is(evt.target.name, 'foobar', 'MessageEvent.target.name is foobar'); + is(evt.target.name, bc.name, 'MessageEvent.target.name is bc.name'); + is(evt.data, "Hello world from the window!", "Message received from the window"); + bc.postMessage("Hello world from the iframe!"); +} + + </script> +</body> +</html> + + diff --git a/dom/broadcastchannel/tests/iframe_mozbrowser.html b/dom/broadcastchannel/tests/iframe_mozbrowser.html new file mode 100644 index 000000000..dbcf63d88 --- /dev/null +++ b/dom/broadcastchannel/tests/iframe_mozbrowser.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>MozBrowser iframe</title> +</head> +<body> + <script type="application/javascript;version=1.7"> + +var bc = new BroadcastChannel('foobar'); +bc.postMessage('This is wrong!'); + + </script> +</body> +</html> diff --git a/dom/broadcastchannel/tests/iframe_mozbrowser2.html b/dom/broadcastchannel/tests/iframe_mozbrowser2.html new file mode 100644 index 000000000..dbcf63d88 --- /dev/null +++ b/dom/broadcastchannel/tests/iframe_mozbrowser2.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>MozBrowser iframe</title> +</head> +<body> + <script type="application/javascript;version=1.7"> + +var bc = new BroadcastChannel('foobar'); +bc.postMessage('This is wrong!'); + + </script> +</body> +</html> diff --git a/dom/broadcastchannel/tests/mochitest.ini b/dom/broadcastchannel/tests/mochitest.ini new file mode 100644 index 000000000..6dff47db4 --- /dev/null +++ b/dom/broadcastchannel/tests/mochitest.ini @@ -0,0 +1,24 @@ +[DEFAULT] +support-files = + iframe_broadcastchannel.html + broadcastchannel_sharedWorker.js + broadcastchannel_worker.js + broadcastchannel_worker_alive.js + broadcastchannel_worker_any.js + file_mozbrowser.html + file_mozbrowser2.html + iframe_mozbrowser.html + iframe_mozbrowser2.html + +[test_broadcastchannel_any.html] +[test_broadcastchannel_basic.html] +[test_broadcastchannel_close.html] +[test_broadcastchannel_close2.html] +[test_broadcastchannel_self.html] +[test_broadcastchannel_sharedWorker.html] +[test_broadcastchannel_worker.html] +[test_broadcastchannel_worker_alive.html] +[test_bfcache.html] +[test_invalidState.html] +[test_ordering.html] +[test_dataCloning.html] diff --git a/dom/broadcastchannel/tests/test_bfcache.html b/dom/broadcastchannel/tests/test_bfcache.html new file mode 100644 index 000000000..e42496bac --- /dev/null +++ b/dom/broadcastchannel/tests/test_bfcache.html @@ -0,0 +1,96 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for bfcache and BroadcastChannel</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <script type="application/javascript"> + + /* This test is hard to follow so here a quick description of what it is about. + * We want to test that when BroadcastChannel is used in a bfcached page, + * this page is fully removed by bfcache. + * + * To test it we have 2 pages (testUrl1 and testUrl2). + * The steps are: + * - we show testUrl1. When this is shown page1Shown is called. + * - page1Shown creates a BroadcastChannel object, then it loads testUrl2. + * - page2Shown is called by testUrl2. + * - Based on expectedPersisted we use or not the BroadcastChannel object of + * testUrl1. + * - Then we call history.back() and testUrl1 will be loaded again. + * - when page1Shown is called by testUrl1, if BroadcastChannel has been used + * when testUrl2 was shown, we want event.persisted be false, otherwise + * true. + */ + var testUrl1 = "data:text/html,<script>onpageshow = function(e) { opener.page1Shown(e); };<" + "/script>"; + var testUrl2 = "data:text/html,<script>onpageshow = function(e) { opener.page2Shown(e); };<" + "/script>"; + + var testWin; + var counter = 0; + var expectedPersisted = false; + var bc; + + function page1Shown(e) { + info("Page1Shown: " + testWin.location.href); + + if (counter == 0) { + ok(!e.persisted, "test page should have been persisted initially"); + + bc = new testWin.BroadcastChannel('a'); + + SimpleTest.executeSoon(function() { + info("New location: " + testUrl2); + testWin.location.href = testUrl2; + }); + } else { + is(e.persisted, expectedPersisted, "test page should have been persisted in pageshow"); + testWin.close(); + runTest(); + } + + counter++; + } + + function page2Shown(e) { + info("Page2Shown: " + testWin.location.href); + + if (!expectedPersisted) { + SimpleTest.executeSoon(function() { + info("Posting a message."); + bc.postMessage(42); + }); + } + + SimpleTest.executeSoon(function() { + info("Going back"); + testWin.history.back(); + }); + } + + var tests = [ + { expectedPersisted: true }, + { expectedPersisted: false }, + ]; + + function runTest() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + + counter = 0; + expectedPersisted = test.expectedPersisted; + testWin = window.open(testUrl1); + } + + SimpleTest.waitForExplicitFinish(); + runTest(); + + </script> +</body> +</html> diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_any.html b/dom/broadcastchannel/tests/test_broadcastchannel_any.html new file mode 100644 index 000000000..2225e7cad --- /dev/null +++ b/dom/broadcastchannel/tests/test_broadcastchannel_any.html @@ -0,0 +1,138 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for BroadcastChannel</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<div id="content"></div> + +<script type="application/javascript"> + +var tests = [ + 'hello world', + 123, + null, + true, + new Date(), + [ 1, 'test', true, new Date() ], + { a: true, b: null, c: new Date(), d: [ true, false, {} ] }, + new Blob([123], { type: 'plain/text' }) +]; + +var currentTest = null; + +function getType(a) { + if (a === null || a === undefined) + return 'null'; + + if (Array.isArray(a)) + return 'array'; + + if (typeof a == 'object') + return 'object'; + + return 'primitive'; +} + +function compare(a, b) { + is (getType(a), getType(b), 'Type matches'); + + var type = getType(a); + if (type == 'array') { + is (a.length, b.length, 'Array.length matches'); + for (var i = 0; i < a.length; ++i) { + compare(a[i], b[i]); + } + + return; + } + + if (type == 'object') { + ok (a !== b, 'They should not match'); + + var aProps = []; + for (var p in a) aProps.push(p); + + var bProps = []; + for (var p in b) bProps.push(p); + + is (aProps.length, bProps.length, 'Props match'); + is (aProps.sort().toSource(), bProps.sort().toSource(), 'Props match - using toSource()'); + + for (var p in a) { + compare(a[p], b[p]); + } + + return; + } + + if (type != 'null') { + is (a.toSource(), b.toSource(), 'Matching using toSource()'); + } +} + +function runTest() { + var count = 2; + + var bc = new BroadcastChannel("foobar"); + ok(bc, "BroadcastChannel can be created"); + + bc.onmessage = function(event) { + ok(count < 2, "Still comparing..."); + info("bc: " + currentTest); + compare(event.data, currentTest); + ++count; + next(); + } + + var bc2 = new BroadcastChannel("foobar"); + ok(bc2, "BroadcastChannel can be created"); + + var toSkip = true; + bc2.onmessage = function(event) { + toSkip = !toSkip; + if (toSkip) return; + + ok(count < 2, "Still comparing..."); + info("bc2: " + currentTest); + compare(event.data, currentTest); + ++count; + next(); + } + + function next() { + if (count < 2) { + return; + } + + is(count, 2, "Just 2 comparations"); + count = 0; + + if (!tests.length) { + SimpleTest.finish(); + return; + } + + currentTest = tests.shift(); + bc.postMessage(currentTest); + info("Posted: " + currentTest); + } + + var worker = new Worker("broadcastchannel_worker_any.js"); + worker.onmessage = function(event) { + if (event.data == "READY") { + next(); + } + }; +} + +SimpleTest.waitForExplicitFinish(); +runTest(); + +</script> +</body> +</html> + diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_basic.html b/dom/broadcastchannel/tests/test_broadcastchannel_basic.html new file mode 100644 index 000000000..45fa3c4a9 --- /dev/null +++ b/dom/broadcastchannel/tests/test_broadcastchannel_basic.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for BroadcastChannel</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<div id="content"></div> + +<script type="application/javascript"> + +function runTest() { + addEventListener('message', receiveMessage, false); + function receiveMessage(evt) { + if (evt.data.status == 'OK') { + ok(true, evt.data.message); + } else if (evt.data.status == 'KO') { + ok(false, evt.data.message); + } else { + ok(false, "Unknown message"); + } + } + + ok("BroadcastChannel" in window, "BroadcastChannel exists"); + + var bc = new BroadcastChannel("foobar"); + ok(bc, "BroadcastChannel can be created"); + is(bc.name, 'foobar', "BroadcastChannel.name is foobar"); + + ok("postMessage" in bc, "BroadcastChannel has postMessage() method"); + + bc.onmessage = function(evt) { + ok(evt instanceof MessageEvent, "This is a MessageEvent"); + is(evt.target, bc, "MessageEvent.target is bc"); + is(evt.target.name, 'foobar', "MessageEvent.target.name is foobar"); + is(evt.target.name, bc.name, "MessageEvent.target.name == bc.name"); + ok(evt.origin.indexOf('http://mochi.test:8888') == 0, "MessageEvent.origin is correct"); + is(evt.data, "Hello world from the iframe!", "The message from the iframe has been received!"); + SimpleTest.finish(); + } + + var div = document.getElementById("content"); + ok(div, "Parent exists"); + + var ifr = document.createElement("iframe"); + ifr.addEventListener("load", iframeLoaded, false); + ifr.setAttribute('src', "iframe_broadcastchannel.html"); + div.appendChild(ifr); + + function iframeLoaded() { + bc.postMessage("Hello world from the window!"); + } + + // A leak test + var dummyBc = new BroadcastChannel("dont_leak_this"); + dummyBc.foo = "bar"; + // don't add message listener! +} + +SimpleTest.waitForExplicitFinish(); +runTest(); + +</script> +</body> +</html> + diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_close.html b/dom/broadcastchannel/tests/test_broadcastchannel_close.html new file mode 100644 index 000000000..84d41db4f --- /dev/null +++ b/dom/broadcastchannel/tests/test_broadcastchannel_close.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for BroadcastChannel</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<div id="content"></div> + +<script type="application/javascript"> + +function runTest() { + var receiver = new BroadcastChannel('foo'); + var sequence = [ '2', 'done' ]; + receiver.onmessage = function(e) { + if (!sequence.length) { + ok (false, 'No more data is expected'); + return; + } + + var data = sequence.shift(); + is(e.data, data); + + if (!sequence.length) { + SimpleTest.executeSoon(function() { + SimpleTest.finish(); + }); + } + } + + var x = new BroadcastChannel('foo'); + x.close(); + try { + x.postMessage('1'); + ok(false, "PostMessage should throw if called after a close()."); + } catch(e) { + ok(true, "PostMessage should throw if called after a close()."); + } + + var y = new BroadcastChannel('foo'); + y.postMessage('2'); + y.close(); + try { + y.postMessage('3'); + ok(false, "PostMessage should throw if called after a close()."); + } catch(e) { + ok(true, "PostMessage should throw if called after a close()."); + } + + var z = new BroadcastChannel('foo'); + z.postMessage('done'); +} + +SimpleTest.waitForExplicitFinish(); +runTest(); + +</script> +</body> +</html> diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_close2.html b/dom/broadcastchannel/tests/test_broadcastchannel_close2.html new file mode 100644 index 000000000..a8a748c46 --- /dev/null +++ b/dom/broadcastchannel/tests/test_broadcastchannel_close2.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for BroadcastChannel</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<div id="content"></div> + +<script type="application/javascript"> + +function runTest() { + var c1 = new BroadcastChannel('foo'); + var c2 = new BroadcastChannel('foo'); + + var status = []; + c2.onmessage = function(e) { + status.push(e.data); + if (status.length == 2) { + is (status[0], 'first', "First message has been received"); + is (status[1], 'second', "Second message has been received"); + SimpleTest.finish(); + } + + c2.close(); + } + + c1.postMessage('first'); + c1.postMessage('second'); +} + +SimpleTest.waitForExplicitFinish(); +runTest(); + +</script> +</body> +</html> diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_self.html b/dom/broadcastchannel/tests/test_broadcastchannel_self.html new file mode 100644 index 000000000..32df390be --- /dev/null +++ b/dom/broadcastchannel/tests/test_broadcastchannel_self.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for BroadcastChannel</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<div id="content"></div> + +<script type="application/javascript"> + +function runTest() { + x = new BroadcastChannel('foo'); + y = new BroadcastChannel('foo'); + + function func(e) { + is(e.target, y, "The target is !x"); + + SimpleTest.executeSoon(function() { + SimpleTest.finish(); + }); + } + + x.onmessage = func; + y.onmessage = func; + + x.postMessage('foo'); +} + +SimpleTest.waitForExplicitFinish(); +runTest(); + +</script> +</body> +</html> + diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html b/dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html new file mode 100644 index 000000000..16f847aca --- /dev/null +++ b/dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html @@ -0,0 +1,52 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM BroadcastChannel in SharedWorkers +--> +<head> + <title>Test for BroadcastChannel in SharedWorkers</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" language="javascript"> + +function runTests() { + var worker = new SharedWorker("broadcastchannel_sharedWorker.js"); + + var bc = new BroadcastChannel('foobar'); + + worker.port.onmessage = function(event) { + if (event.data == "READY") { + ok(true, "SharedWorker is ready!"); + bc.postMessage('hello world from the window'); + } else { + ok(false, "Something wrong happened"); + } + }; + + bc.onmessage = function(event) { + is("hello world from the worker", event.data, "The message matches!"); + bc.close(); + SimpleTest.finish(); + } + + worker.port.postMessage('go'); +} + +SimpleTest.waitForExplicitFinish(); +runTests(); + +</script> +</pre> +</body> +</html> diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_worker.html b/dom/broadcastchannel/tests/test_broadcastchannel_worker.html new file mode 100644 index 000000000..42e93cc1e --- /dev/null +++ b/dom/broadcastchannel/tests/test_broadcastchannel_worker.html @@ -0,0 +1,62 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM BroadcastChannel in workers +--> +<head> + <title>Test for BroadcastChannel in workers</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" language="javascript"> + +function testWorker(x) { + var worker = new Worker("broadcastchannel_worker.js"); + + var bc = new BroadcastChannel('foobar'); + + worker.onmessage = function(event) { + if (event.data == "READY") { + ok(true, "Worker is ready!"); + bc.postMessage('hello world from the window'); + } else { + ok(false, "Something wrong happened"); + } + }; + + bc.onmessage = function(event) { + is("hello world from the worker", event.data, "The message matches!"); + bc.close(); + runTests(); + } + + worker.postMessage(x); +} + +var tests = [ 0, 3 ]; +function runTests() { + if (tests.length == 0) { + SimpleTest.finish(); + return; + } + + testWorker(tests.shift()); +} + +SimpleTest.waitForExplicitFinish(); +runTests(); + +</script> +</pre> +</body> +</html> diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_worker_alive.html b/dom/broadcastchannel/tests/test_broadcastchannel_worker_alive.html new file mode 100644 index 000000000..8b1b03c4e --- /dev/null +++ b/dom/broadcastchannel/tests/test_broadcastchannel_worker_alive.html @@ -0,0 +1,56 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM BroadcastChannel in workers +--> +<head> + <title>Test for BroadcastChannel in workers</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" language="javascript"> + +function runTests() { + var id = 0; + (new BroadcastChannel('foobar')).onmessage = function(event) { + info("MSG: " + event.data); + + if (event.data == "READY") { + ok(true, "Worker is ready!"); + } else { + is(id, event.data, "The message is correct: " + id); + } + + for (var i = 0; i < 3; ++i) { + SpecialPowers.forceCC(); + SpecialPowers.forceGC(); + } + + if (id == 5) { + SimpleTest.finish(); + return; + } + + event.target.postMessage(++id); + }; + + new Worker("broadcastchannel_worker_alive.js"); +} + +SimpleTest.waitForExplicitFinish(); +runTests(); + +</script> +</pre> +</body> +</html> diff --git a/dom/broadcastchannel/tests/test_dataCloning.html b/dom/broadcastchannel/tests/test_dataCloning.html new file mode 100644 index 000000000..6aa164e94 --- /dev/null +++ b/dom/broadcastchannel/tests/test_dataCloning.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for BroadcastChannel.postMessage invalid State</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<div id="content"></div> + +<script type="application/javascript"> + + +let c = new BroadcastChannel("foo"); + +try { + c.postMessage(Symbol()); + ok(false, "This should throw!"); +} catch(e) { + ok(true, "This should throw!"); + is(e.name, "DataCloneError", "Correct DataCloneError exception thrown"); +} + +</script> +</body> +</html> diff --git a/dom/broadcastchannel/tests/test_invalidState.html b/dom/broadcastchannel/tests/test_invalidState.html new file mode 100644 index 000000000..8e4a1df35 --- /dev/null +++ b/dom/broadcastchannel/tests/test_invalidState.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for BroadcastChannel.postMessage invalid State</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<div id="content"></div> + +<script type="application/javascript"> + +var c = new BroadcastChannel("foo"); +c.close(); + +try { + c.postMessage("bar"); + ok(false, "This should throw!"); +} catch(e) { + ok(true, "This should throw!"); + is(e.name, "InvalidStateError", "Correct invalid-state exception thrown"); +} + +</script> +</body> +</html> + diff --git a/dom/broadcastchannel/tests/test_ordering.html b/dom/broadcastchannel/tests/test_ordering.html new file mode 100644 index 000000000..eab1f955e --- /dev/null +++ b/dom/broadcastchannel/tests/test_ordering.html @@ -0,0 +1,65 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for BroadcastChannel.postMessage invalid State</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<div id="content"></div> + +<script type="application/javascript"> + +let c1 = new BroadcastChannel('order'); +let c2 = new BroadcastChannel('order'); +let c3 = new BroadcastChannel('order'); + +let events = []; +let doneCount = 0; + +function whichBC(bc) { + if (bc == c1) return "c1"; + if (bc == c2) return "c2"; + if (bc == c3) return "c3"; + return "What?!?"; +} + +function handler(e) { + events.push(e); + if (e.data == 'done') { + doneCount++; + if (doneCount == 2) { + is(events.length, 6, "Correct length"); + is(whichBC(events[0].target), "c2", 'target for event 0'); + is(events[0].data, 'from c1'); + is(whichBC(events[1].target), "c3", 'target for event 1'); + is(events[1].data, 'from c1'); + is(whichBC(events[2].target), "c1", 'target for event 2'); + is(events[2].data, 'from c3'); + is(whichBC(events[3].target), "c2", 'target for event 3'); + is(events[3].data, 'from c3'); + is(whichBC(events[4].target), "c1", 'target for event 4'); + is(events[4].data, 'done'); + is(whichBC(events[5].target), "c3", 'target for event 5'); + is(events[5].data, 'done'); + + SimpleTest.finish(); + } + } +} + +c1.onmessage = handler; +c2.onmessage = handler; +c3.onmessage = handler; + +c1.postMessage('from c1'); +c3.postMessage('from c3'); +c2.postMessage('done'); + +SimpleTest.waitForExplicitFinish(); + +</script> +</body> +</html> + |