author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Tue, 09 Aug 2016 15:44:51 +0200 | |
changeset 308748 | 6cf0089510fad8deb866136f5b92bbced9498447 |
parent 308654 | 0813514a828bf931605bd22596d8fe876c1bb368 (current diff) |
parent 308747 | 7bbd0953ab0b4d9ce9dab06e86a0f245b568b72c (diff) |
child 308753 | 4dd7d0ccddf9d06268d2061efe525226b5cf7c2b |
child 308793 | 613fec9a571e48c64918ebd0a8153096300ff992 |
child 308819 | 99f20b0c58d01780b427936cc216403535e6b46c |
push id | 30547 |
push user | cbook@mozilla.com |
push date | Tue, 09 Aug 2016 13:45:15 +0000 |
treeherder | mozilla-central@6cf0089510fa [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 51.0a1 |
first release with | nightly linux32
6cf0089510fa
/
51.0a1
/
20160810030202
/
files
nightly linux64
6cf0089510fa
/
51.0a1
/
20160810030202
/
files
nightly mac
6cf0089510fa
/
51.0a1
/
20160810030202
/
files
nightly win32
6cf0089510fa
/
51.0a1
/
20160810030202
/
files
nightly win64
6cf0089510fa
/
51.0a1
/
20160810030202
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
51.0a1
/
20160810030202
/
pushlog to previous
nightly linux64
51.0a1
/
20160810030202
/
pushlog to previous
nightly mac
51.0a1
/
20160810030202
/
pushlog to previous
nightly win32
51.0a1
/
20160810030202
/
pushlog to previous
nightly win64
51.0a1
/
20160810030202
/
pushlog to previous
|
--- a/accessible/base/DocManager.cpp +++ b/accessible/base/DocManager.cpp @@ -84,24 +84,33 @@ DocManager::FindAccessibleInCache(nsINod } return nullptr; } void DocManager::NotifyOfDocumentShutdown(DocAccessible* aDocument, nsIDocument* aDOMDocument) { + // We need to remove listeners in both cases, when document is being shutdown + // or when accessibility service is being shut down as well. + RemoveListeners(aDOMDocument); + + // Document will already be removed when accessibility service is shutting + // down so we do not need to remove it twice. + if (nsAccessibilityService::IsShutdown()) { + return; + } + xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument); if (xpcDoc) { xpcDoc->Shutdown(); mXPCDocumentCache.Remove(aDocument); } mDocAccessibleCache.Remove(aDOMDocument); - RemoveListeners(aDOMDocument); } void DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc) { xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc); if (doc) { doc->Shutdown(); @@ -525,29 +534,42 @@ DocManager::CreateDocOrRootAccessible(ns } //////////////////////////////////////////////////////////////////////////////// // DocManager static void DocManager::ClearDocCache() { - // This unusual do-one-element-per-iterator approach is required because each - // DocAccessible is removed elsewhere upon its Shutdown() method being - // called, which invalidates the existing iterator. while (mDocAccessibleCache.Count() > 0) { auto iter = mDocAccessibleCache.Iter(); MOZ_ASSERT(!iter.Done()); DocAccessible* docAcc = iter.UserData(); NS_ASSERTION(docAcc, "No doc accessible for the object in doc accessible cache!"); if (docAcc) { docAcc->Shutdown(); } + + iter.Remove(); } + + // Ensure that all xpcom accessible documents are shut down as well. + while (mXPCDocumentCache.Count() > 0) { + auto iter = mXPCDocumentCache.Iter(); + MOZ_ASSERT(!iter.Done()); + xpcAccessibleDocument* xpcDoc = iter.UserData(); + NS_ASSERTION(xpcDoc, "No xpc doc for the object in xpc doc cache!"); + + if (xpcDoc) { + xpcDoc->Shutdown(); + } + + iter.Remove(); + } } void DocManager::RemoteDocAdded(DocAccessibleParent* aDoc) { if (!sRemoteDocuments) { sRemoteDocuments = new nsTArray<DocAccessibleParent*>; ClearOnShutdown(&sRemoteDocuments);
--- a/accessible/base/NotificationController.cpp +++ b/accessible/base/NotificationController.cpp @@ -214,34 +214,16 @@ NotificationController::WillRefresh(mozi NS_ASSERTION(mContentInsertions.Count() == 0, "Pending content insertions while initial accessible tree isn't created!"); } // Initialize scroll support if needed. if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized)) mDocument->AddScrollListener(); - // Process content inserted notifications to update the tree. Process other - // notifications like DOM events and then flush event queue. If any new - // notifications are queued during this processing then they will be processed - // on next refresh. If notification processing queues up new events then they - // are processed in this refresh. If events processing queues up new events - // then new events are processed on next refresh. - // Note: notification processing or event handling may shut down the owning - // document accessible. - - // Process only currently queued content inserted notifications. - for (auto iter = mContentInsertions.ConstIter(); !iter.Done(); iter.Next()) { - mDocument->ProcessContentInserted(iter.Key(), iter.UserData()); - if (!mDocument) { - return; - } - } - mContentInsertions.Clear(); - // Process rendered text change notifications. for (auto iter = mTextHash.Iter(); !iter.Done(); iter.Next()) { nsCOMPtrHashKey<nsIContent>* entry = iter.Get(); nsIContent* textNode = entry->GetKey(); Accessible* textAcc = mDocument->GetAccessible(textNode); // If the text node is not in tree or doesn't have frame then this case should // have been handled already by content removal notifications. @@ -306,27 +288,37 @@ NotificationController::WillRefresh(mozi if (logging::IsEnabled(logging::eTree | logging::eText)) { logging::MsgBegin("TREE", "text node gains new content; doc: %p", mDocument); logging::Node("container", containerElm); logging::Node("content", textNode); logging::MsgEnd(); } #endif - // Make sure the text node is in accessible document still. Accessible* container = mDocument->AccessibleOrTrueContainer(containerNode); MOZ_ASSERT(container, "Text node having rendered text hasn't accessible document!"); if (container) { - mDocument->ProcessContentInserted(container, textNode); + nsTArray<nsCOMPtr<nsIContent>>* list = + mContentInsertions.LookupOrAdd(container); + list->AppendElement(textNode); } } } mTextHash.Clear(); + // Process content inserted notifications to update the tree. + for (auto iter = mContentInsertions.ConstIter(); !iter.Done(); iter.Next()) { + mDocument->ProcessContentInserted(iter.Key(), iter.UserData()); + if (!mDocument) { + return; + } + } + mContentInsertions.Clear(); + // Bind hanging child documents. uint32_t hangingDocCnt = mHangingChildDocuments.Length(); nsTArray<RefPtr<DocAccessible>> newChildDocs; for (uint32_t idx = 0; idx < hangingDocCnt; idx++) { DocAccessible* childDoc = mHangingChildDocuments[idx]; if (childDoc->IsDefunct()) continue;
--- a/accessible/base/nsAccessibilityService.cpp +++ b/accessible/base/nsAccessibilityService.cpp @@ -15,17 +15,16 @@ #include "HTMLElementAccessibles.h" #include "HTMLImageMapAccessible.h" #include "HTMLLinkAccessible.h" #include "HTMLListAccessible.h" #include "HTMLSelectAccessible.h" #include "HTMLTableAccessibleWrap.h" #include "HyperTextAccessibleWrap.h" #include "RootAccessible.h" -#include "nsAccessiblePivot.h" #include "nsAccUtils.h" #include "nsArrayUtils.h" #include "nsAttrName.h" #include "nsEventShell.h" #include "nsIURI.h" #include "OuterDocAccessible.h" #include "Platform.h" #include "Role.h" @@ -260,16 +259,17 @@ static const MarkupMapInfo sMarkupMapLis //////////////////////////////////////////////////////////////////////////////// // nsAccessibilityService //////////////////////////////////////////////////////////////////////////////// nsAccessibilityService *nsAccessibilityService::gAccessibilityService = nullptr; ApplicationAccessible* nsAccessibilityService::gApplicationAccessible = nullptr; xpcAccessibleApplication* nsAccessibilityService::gXPCApplicationAccessible = nullptr; bool nsAccessibilityService::gIsShutdown = true; +bool nsAccessibilityService::gIsPlatformCaller = false; nsAccessibilityService::nsAccessibilityService() : DocManager(), FocusManager(), mMarkupMaps(ArrayLength(sMarkupMapList)) { } nsAccessibilityService::~nsAccessibilityService() { @@ -335,18 +335,16 @@ nsAccessibilityService::ListenersChanged return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // nsISupports NS_IMPL_ISUPPORTS_INHERITED(nsAccessibilityService, DocManager, - nsIAccessibilityService, - nsIAccessibleRetrieval, nsIObserver, nsIListenerChangeListener, nsISelectionListener) // from SelectionManager //////////////////////////////////////////////////////////////////////////////// // nsIObserver NS_IMETHODIMP @@ -354,39 +352,34 @@ nsAccessibilityService::Observe(nsISuppo const char16_t *aData) { if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) Shutdown(); return NS_OK; } -// nsIAccessibilityService void nsAccessibilityService::NotifyOfAnchorJumpTo(nsIContent* aTargetNode) { nsIDocument* documentNode = aTargetNode->GetUncomposedDoc(); if (documentNode) { DocAccessible* document = GetDocAccessible(documentNode); if (document) document->SetAnchorJump(aTargetNode); } } -// nsIAccessibilityService void nsAccessibilityService::FireAccessibleEvent(uint32_t aEvent, Accessible* aTarget) { nsEventShell::FireEvent(aEvent, aTarget); } -//////////////////////////////////////////////////////////////////////////////// -// nsIAccessibilityService - Accessible* nsAccessibilityService::GetRootDocumentAccessible(nsIPresShell* aPresShell, bool aCanCreate) { nsIPresShell* ps = aPresShell; nsIDocument* documentNode = aPresShell->GetDocument(); if (documentNode) { nsCOMPtr<nsIDocShellTreeItem> treeItem(documentNode->GetDocShell()); @@ -732,290 +725,233 @@ void nsAccessibilityService::RecreateAccessible(nsIPresShell* aPresShell, nsIContent* aContent) { DocAccessible* document = GetDocAccessible(aPresShell); if (document) document->RecreateAccessible(aContent); } -//////////////////////////////////////////////////////////////////////////////// -// nsIAccessibleRetrieval - -NS_IMETHODIMP -nsAccessibilityService::GetApplicationAccessible(nsIAccessible** aAccessibleApplication) -{ - NS_ENSURE_ARG_POINTER(aAccessibleApplication); - - NS_IF_ADDREF(*aAccessibleApplication = XPCApplicationAcc()); - return NS_OK; -} - -NS_IMETHODIMP -nsAccessibilityService::GetAccessibleFor(nsIDOMNode *aNode, - nsIAccessible **aAccessible) -{ - NS_ENSURE_ARG_POINTER(aAccessible); - *aAccessible = nullptr; - if (!aNode) - return NS_OK; - - nsCOMPtr<nsINode> node(do_QueryInterface(aNode)); - if (!node) - return NS_ERROR_INVALID_ARG; - - DocAccessible* document = GetDocAccessible(node->OwnerDoc()); - if (document) - NS_IF_ADDREF(*aAccessible = ToXPC(document->GetAccessible(node))); - - return NS_OK; -} - -NS_IMETHODIMP +void nsAccessibilityService::GetStringRole(uint32_t aRole, nsAString& aString) { #define ROLE(geckoRole, stringRole, atkRole, \ macRole, msaaRole, ia2Role, nameRule) \ case roles::geckoRole: \ CopyUTF8toUTF16(stringRole, aString); \ - return NS_OK; + return; switch (aRole) { #include "RoleMap.h" default: aString.AssignLiteral("unknown"); - return NS_OK; + return; } #undef ROLE } -NS_IMETHODIMP +void nsAccessibilityService::GetStringStates(uint32_t aState, uint32_t aExtraState, nsISupports **aStringStates) { RefPtr<DOMStringList> stringStates = new DOMStringList(); uint64_t state = nsAccUtils::To64State(aState, aExtraState); // states - if (state & states::UNAVAILABLE) + if (state & states::UNAVAILABLE) { stringStates->Add(NS_LITERAL_STRING("unavailable")); - if (state & states::SELECTED) + } + if (state & states::SELECTED) { stringStates->Add(NS_LITERAL_STRING("selected")); - if (state & states::FOCUSED) + } + if (state & states::FOCUSED) { stringStates->Add(NS_LITERAL_STRING("focused")); - if (state & states::PRESSED) + } + if (state & states::PRESSED) { stringStates->Add(NS_LITERAL_STRING("pressed")); - if (state & states::CHECKED) + } + if (state & states::CHECKED) { stringStates->Add(NS_LITERAL_STRING("checked")); - if (state & states::MIXED) + } + if (state & states::MIXED) { stringStates->Add(NS_LITERAL_STRING("mixed")); - if (state & states::READONLY) + } + if (state & states::READONLY) { stringStates->Add(NS_LITERAL_STRING("readonly")); - if (state & states::HOTTRACKED) + } + if (state & states::HOTTRACKED) { stringStates->Add(NS_LITERAL_STRING("hottracked")); - if (state & states::DEFAULT) + } + if (state & states::DEFAULT) { stringStates->Add(NS_LITERAL_STRING("default")); - if (state & states::EXPANDED) + } + if (state & states::EXPANDED) { stringStates->Add(NS_LITERAL_STRING("expanded")); - if (state & states::COLLAPSED) + } + if (state & states::COLLAPSED) { stringStates->Add(NS_LITERAL_STRING("collapsed")); - if (state & states::BUSY) + } + if (state & states::BUSY) { stringStates->Add(NS_LITERAL_STRING("busy")); - if (state & states::FLOATING) + } + if (state & states::FLOATING) { stringStates->Add(NS_LITERAL_STRING("floating")); - if (state & states::ANIMATED) + } + if (state & states::ANIMATED) { stringStates->Add(NS_LITERAL_STRING("animated")); - if (state & states::INVISIBLE) + } + if (state & states::INVISIBLE) { stringStates->Add(NS_LITERAL_STRING("invisible")); - if (state & states::OFFSCREEN) + } + if (state & states::OFFSCREEN) { stringStates->Add(NS_LITERAL_STRING("offscreen")); - if (state & states::SIZEABLE) + } + if (state & states::SIZEABLE) { stringStates->Add(NS_LITERAL_STRING("sizeable")); - if (state & states::MOVEABLE) + } + if (state & states::MOVEABLE) { stringStates->Add(NS_LITERAL_STRING("moveable")); - if (state & states::SELFVOICING) + } + if (state & states::SELFVOICING) { stringStates->Add(NS_LITERAL_STRING("selfvoicing")); - if (state & states::FOCUSABLE) + } + if (state & states::FOCUSABLE) { stringStates->Add(NS_LITERAL_STRING("focusable")); - if (state & states::SELECTABLE) + } + if (state & states::SELECTABLE) { stringStates->Add(NS_LITERAL_STRING("selectable")); - if (state & states::LINKED) + } + if (state & states::LINKED) { stringStates->Add(NS_LITERAL_STRING("linked")); - if (state & states::TRAVERSED) + } + if (state & states::TRAVERSED) { stringStates->Add(NS_LITERAL_STRING("traversed")); - if (state & states::MULTISELECTABLE) + } + if (state & states::MULTISELECTABLE) { stringStates->Add(NS_LITERAL_STRING("multiselectable")); - if (state & states::EXTSELECTABLE) + } + if (state & states::EXTSELECTABLE) { stringStates->Add(NS_LITERAL_STRING("extselectable")); - if (state & states::PROTECTED) + } + if (state & states::PROTECTED) { stringStates->Add(NS_LITERAL_STRING("protected")); - if (state & states::HASPOPUP) + } + if (state & states::HASPOPUP) { stringStates->Add(NS_LITERAL_STRING("haspopup")); - if (state & states::REQUIRED) + } + if (state & states::REQUIRED) { stringStates->Add(NS_LITERAL_STRING("required")); - if (state & states::ALERT) + } + if (state & states::ALERT) { stringStates->Add(NS_LITERAL_STRING("alert")); - if (state & states::INVALID) + } + if (state & states::INVALID) { stringStates->Add(NS_LITERAL_STRING("invalid")); - if (state & states::CHECKABLE) + } + if (state & states::CHECKABLE) { stringStates->Add(NS_LITERAL_STRING("checkable")); + } // extraStates - if (state & states::SUPPORTS_AUTOCOMPLETION) + if (state & states::SUPPORTS_AUTOCOMPLETION) { stringStates->Add(NS_LITERAL_STRING("autocompletion")); - if (state & states::DEFUNCT) + } + if (state & states::DEFUNCT) { stringStates->Add(NS_LITERAL_STRING("defunct")); - if (state & states::SELECTABLE_TEXT) + } + if (state & states::SELECTABLE_TEXT) { stringStates->Add(NS_LITERAL_STRING("selectable text")); - if (state & states::EDITABLE) + } + if (state & states::EDITABLE) { stringStates->Add(NS_LITERAL_STRING("editable")); - if (state & states::ACTIVE) + } + if (state & states::ACTIVE) { stringStates->Add(NS_LITERAL_STRING("active")); - if (state & states::MODAL) + } + if (state & states::MODAL) { stringStates->Add(NS_LITERAL_STRING("modal")); - if (state & states::MULTI_LINE) + } + if (state & states::MULTI_LINE) { stringStates->Add(NS_LITERAL_STRING("multi line")); - if (state & states::HORIZONTAL) + } + if (state & states::HORIZONTAL) { stringStates->Add(NS_LITERAL_STRING("horizontal")); - if (state & states::OPAQUE1) + } + if (state & states::OPAQUE1) { stringStates->Add(NS_LITERAL_STRING("opaque")); - if (state & states::SINGLE_LINE) + } + if (state & states::SINGLE_LINE) { stringStates->Add(NS_LITERAL_STRING("single line")); - if (state & states::TRANSIENT) + } + if (state & states::TRANSIENT) { stringStates->Add(NS_LITERAL_STRING("transient")); - if (state & states::VERTICAL) + } + if (state & states::VERTICAL) { stringStates->Add(NS_LITERAL_STRING("vertical")); - if (state & states::STALE) + } + if (state & states::STALE) { stringStates->Add(NS_LITERAL_STRING("stale")); - if (state & states::ENABLED) + } + if (state & states::ENABLED) { stringStates->Add(NS_LITERAL_STRING("enabled")); - if (state & states::SENSITIVE) + } + if (state & states::SENSITIVE) { stringStates->Add(NS_LITERAL_STRING("sensitive")); - if (state & states::EXPANDABLE) + } + if (state & states::EXPANDABLE) { stringStates->Add(NS_LITERAL_STRING("expandable")); + } //unknown states - if (!stringStates->Length()) + if (!stringStates->Length()) { stringStates->Add(NS_LITERAL_STRING("unknown")); + } stringStates.forget(aStringStates); - return NS_OK; } -// nsIAccessibleRetrieval::getStringEventType() -NS_IMETHODIMP +void nsAccessibilityService::GetStringEventType(uint32_t aEventType, nsAString& aString) { NS_ASSERTION(nsIAccessibleEvent::EVENT_LAST_ENTRY == ArrayLength(kEventTypeNames), "nsIAccessibleEvent constants are out of sync to kEventTypeNames"); if (aEventType >= ArrayLength(kEventTypeNames)) { aString.AssignLiteral("unknown"); - return NS_OK; + return; } CopyUTF8toUTF16(kEventTypeNames[aEventType], aString); - return NS_OK; } -// nsIAccessibleRetrieval::getStringRelationType() -NS_IMETHODIMP +void nsAccessibilityService::GetStringRelationType(uint32_t aRelationType, nsAString& aString) { - NS_ENSURE_ARG(aRelationType <= static_cast<uint32_t>(RelationType::LAST)); + NS_ENSURE_TRUE_VOID(aRelationType <= static_cast<uint32_t>(RelationType::LAST)); #define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \ case RelationType::geckoType: \ aString.AssignLiteral(geckoTypeName); \ - return NS_OK; + return; RelationType relationType = static_cast<RelationType>(aRelationType); switch (relationType) { #include "RelationTypeMap.h" default: aString.AssignLiteral("unknown"); - return NS_OK; + return; } #undef RELATIONTYPE } -NS_IMETHODIMP -nsAccessibilityService::GetAccessibleFromCache(nsIDOMNode* aNode, - nsIAccessible** aAccessible) -{ - NS_ENSURE_ARG_POINTER(aAccessible); - *aAccessible = nullptr; - if (!aNode) - return NS_OK; - - nsCOMPtr<nsINode> node(do_QueryInterface(aNode)); - if (!node) - return NS_ERROR_INVALID_ARG; - - // Search for an accessible in each of our per document accessible object - // caches. If we don't find it, and the given node is itself a document, check - // our cache of document accessibles (document cache). Note usually shutdown - // document accessibles are not stored in the document cache, however an - // "unofficially" shutdown document (i.e. not from DocManager) can still - // exist in the document cache. - Accessible* accessible = FindAccessibleInCache(node); - if (!accessible) { - nsCOMPtr<nsIDocument> document(do_QueryInterface(node)); - if (document) - accessible = GetExistingDocAccessible(document); - } - - NS_IF_ADDREF(*aAccessible = ToXPC(accessible)); - return NS_OK; -} - -NS_IMETHODIMP -nsAccessibilityService::CreateAccessiblePivot(nsIAccessible* aRoot, - nsIAccessiblePivot** aPivot) -{ - NS_ENSURE_ARG_POINTER(aPivot); - NS_ENSURE_ARG(aRoot); - *aPivot = nullptr; - - Accessible* accessibleRoot = aRoot->ToInternalAccessible(); - NS_ENSURE_TRUE(accessibleRoot, NS_ERROR_INVALID_ARG); - - nsAccessiblePivot* pivot = new nsAccessiblePivot(accessibleRoot); - NS_ADDREF(*aPivot = pivot); - - return NS_OK; -} - -NS_IMETHODIMP -nsAccessibilityService::SetLogging(const nsACString& aModules) -{ -#ifdef A11Y_LOG - logging::Enable(PromiseFlatCString(aModules)); -#endif - return NS_OK; -} - -NS_IMETHODIMP -nsAccessibilityService::IsLogged(const nsAString& aModule, bool* aIsLogged) -{ - NS_ENSURE_ARG_POINTER(aIsLogged); - *aIsLogged = false; - -#ifdef A11Y_LOG - *aIsLogged = logging::IsEnabled(aModule); -#endif - - return NS_OK; -} - //////////////////////////////////////////////////////////////////////////////// // nsAccessibilityService public Accessible* nsAccessibilityService::CreateAccessible(nsINode* aNode, Accessible* aContext, bool* aIsSubtreeHidden) { @@ -1322,16 +1258,17 @@ nsAccessibilityService::Init() for (uint32_t i = 0; i < ArrayLength(sMarkupMapList); i++) mMarkupMaps.Put(*sMarkupMapList[i].tag, &sMarkupMapList[i]); #ifdef A11Y_LOG logging::CheckEnv(); #endif gAccessibilityService = this; + NS_ADDREF(gAccessibilityService); // will release in Shutdown() if (XRE_IsParentProcess()) gApplicationAccessible = new ApplicationAccessibleWrap(); else gApplicationAccessible = new ApplicationAccessible(); NS_ADDREF(gApplicationAccessible); // will release in Shutdown() gApplicationAccessible->Init(); @@ -1354,16 +1291,25 @@ nsAccessibilityService::Init() PlatformInit(); return true; } void nsAccessibilityService::Shutdown() { + // Application is going to be closed, shutdown accessibility and mark + // accessibility service as shutdown to prevent calls of its methods. + // Don't null accessibility service static member at this point to be safe + // if someone will try to operate with it. + + MOZ_ASSERT(!gIsShutdown, "Accessibility was shutdown already"); + + gIsShutdown = true; + // Remove observers. nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); static const char16_t kShutdownIndicator[] = { '0', 0 }; observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kShutdownIndicator); @@ -1379,34 +1325,29 @@ nsAccessibilityService::Shutdown() uint32_t timerCount = sPluginTimers->Length(); for (uint32_t i = 0; i < timerCount; i++) sPluginTimers->ElementAt(i)->Cancel(); sPluginTimers = nullptr; #endif - // Application is going to be closed, shutdown accessibility and mark - // accessibility service as shutdown to prevent calls of its methods. - // Don't null accessibility service static member at this point to be safe - // if someone will try to operate with it. - - NS_ASSERTION(!gIsShutdown, "Accessibility was shutdown already"); - - gIsShutdown = true; - if (XRE_IsParentProcess()) PlatformShutdown(); gApplicationAccessible->Shutdown(); NS_RELEASE(gApplicationAccessible); gApplicationAccessible = nullptr; NS_IF_RELEASE(gXPCApplicationAccessible); gXPCApplicationAccessible = nullptr; + + NS_RELEASE(gAccessibilityService); + gAccessibilityService = nullptr; + gIsPlatformCaller = false; } already_AddRefed<Accessible> nsAccessibilityService::CreateAccessibleByType(nsIContent* aContent, DocAccessible* aDoc) { nsAutoString role; nsCoreUtils::XBLBindingRole(aContent, role); @@ -1751,19 +1692,16 @@ nsAccessibilityService::MarkupAttributes continue; } nsAccUtils::SetAccAttr(aAttributes, *info->name, *info->value); } } -//////////////////////////////////////////////////////////////////////////////// -// nsIAccessibilityService (DON'T put methods here) - Accessible* nsAccessibilityService::AddNativeRootAccessible(void* aAtkAccessible) { #ifdef MOZ_ACCESSIBILITY_ATK ApplicationAccessible* applicationAcc = ApplicationAcc(); if (!applicationAcc) return nullptr; @@ -1798,48 +1736,16 @@ nsAccessibilityService::HasAccessible(ns DocAccessible* document = GetDocAccessible(node->OwnerDoc()); if (!document) return false; return document->HasAccessible(node); } //////////////////////////////////////////////////////////////////////////////// -// NS_GetAccessibilityService -//////////////////////////////////////////////////////////////////////////////// - -/** - * Return accessibility service; creating one if necessary. - */ -nsresult -NS_GetAccessibilityService(nsIAccessibilityService** aResult) -{ - NS_ENSURE_TRUE(aResult, NS_ERROR_NULL_POINTER); - *aResult = nullptr; - - if (nsAccessibilityService::gAccessibilityService) { - NS_ADDREF(*aResult = nsAccessibilityService::gAccessibilityService); - return NS_OK; - } - - RefPtr<nsAccessibilityService> service = new nsAccessibilityService(); - NS_ENSURE_TRUE(service, NS_ERROR_OUT_OF_MEMORY); - - if (!service->Init()) { - service->Shutdown(); - return NS_ERROR_FAILURE; - } - - statistics::A11yInitialized(); - - NS_ADDREF(*aResult = service); - return NS_OK; -} - -//////////////////////////////////////////////////////////////////////////////// // nsAccessibilityService private (DON'T put methods here) #ifdef MOZ_XUL already_AddRefed<Accessible> nsAccessibilityService::CreateAccessibleForXULTree(nsIContent* aContent, DocAccessible* aDoc) { nsIContent* child = nsTreeUtils::GetDescendantChild(aContent, @@ -1864,16 +1770,48 @@ nsAccessibilityService::CreateAccessible // Table or tree table accessible. RefPtr<Accessible> accessible = new XULTreeGridAccessibleWrap(aContent, aDoc, treeFrame); return accessible.forget(); } #endif +nsAccessibilityService* +GetOrCreateAccService(bool aIsPlatformCaller) +{ + if (aIsPlatformCaller) { + nsAccessibilityService::gIsPlatformCaller = aIsPlatformCaller; + } + + if (!nsAccessibilityService::gAccessibilityService) { + RefPtr<nsAccessibilityService> service = new nsAccessibilityService(); + if (!service->Init()) { + service->Shutdown(); + return nullptr; + } + } + + MOZ_ASSERT(nsAccessibilityService::gAccessibilityService, + "Accessible service is not initialized."); + return nsAccessibilityService::gAccessibilityService; +} + +bool +CanShutdownAccService() +{ + nsAccessibilityService* accService = nsAccessibilityService::gAccessibilityService; + if (!accService) { + return false; + } + return !xpcAccessibilityService::IsInUse() && + !accService->IsPlatformCaller() && !accService->IsShutdown() && + !nsCoreUtils::AccEventObserversExist(); +} + //////////////////////////////////////////////////////////////////////////////// // Services //////////////////////////////////////////////////////////////////////////////// namespace mozilla { namespace a11y { FocusManager*
--- a/accessible/base/nsAccessibilityService.h +++ b/accessible/base/nsAccessibilityService.h @@ -1,26 +1,26 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 __nsAccessibilityService_h__ #define __nsAccessibilityService_h__ -#include "nsIAccessibilityService.h" - #include "mozilla/a11y/DocManager.h" #include "mozilla/a11y/FocusManager.h" #include "mozilla/a11y/Role.h" #include "mozilla/a11y/SelectionManager.h" #include "mozilla/Preferences.h" #include "nsIObserver.h" +#include "nsIAccessibleEvent.h" #include "nsIEventListenerService.h" +#include "xpcAccessibilityService.h" class nsImageFrame; class nsIArray; class nsIPersistentProperties; class nsPluginFrame; class nsITreeView; namespace mozilla { @@ -63,50 +63,68 @@ struct MarkupMapInfo { }; } // namespace a11y } // namespace mozilla class nsAccessibilityService final : public mozilla::a11y::DocManager, public mozilla::a11y::FocusManager, public mozilla::a11y::SelectionManager, - public nsIAccessibilityService, public nsIListenerChangeListener, public nsIObserver { public: typedef mozilla::a11y::Accessible Accessible; typedef mozilla::a11y::DocAccessible DocAccessible; // nsIListenerChangeListener NS_IMETHOD ListenersChanged(nsIArray* aEventChanges) override; protected: - virtual ~nsAccessibilityService(); + ~nsAccessibilityService(); public: NS_DECL_ISUPPORTS_INHERITED - NS_DECL_NSIACCESSIBLERETRIEVAL NS_DECL_NSIOBSERVER - // nsIAccessibilityService - virtual Accessible* GetRootDocumentAccessible(nsIPresShell* aPresShell, - bool aCanCreate) override; + Accessible* GetRootDocumentAccessible(nsIPresShell* aPresShell, + bool aCanCreate); already_AddRefed<Accessible> CreatePluginAccessible(nsPluginFrame* aFrame, nsIContent* aContent, Accessible* aContext); /** * Adds/remove ATK root accessible for gtk+ native window to/from children * of the application accessible. */ - virtual Accessible* AddNativeRootAccessible(void* aAtkAccessible) override; - virtual void RemoveNativeRootAccessible(Accessible* aRootAccessible) override; + Accessible* AddNativeRootAccessible(void* aAtkAccessible); + void RemoveNativeRootAccessible(Accessible* aRootAccessible); + + bool HasAccessible(nsIDOMNode* aDOMNode); + + /** + * Get a string equivalent for an accessilbe role value. + */ + void GetStringRole(uint32_t aRole, nsAString& aString); - virtual bool HasAccessible(nsIDOMNode* aDOMNode) override; + /** + * Get a string equivalent for an accessible state/extra state. + */ + void GetStringStates(uint32_t aState, uint32_t aExtraState, + nsISupports **aStringStates); + + /** + * Get a string equivalent for an accessible event value. + */ + void GetStringEventType(uint32_t aEventType, nsAString& aString); + + /** + * Get a string equivalent for an accessible relation type. + */ + void GetStringRelationType(uint32_t aRelationType, nsAString& aString); // nsAccesibilityService /** * Notification used to update the accessible tree when deck panel is * switched. */ void DeckPanelSwitched(nsIPresShell* aPresShell, nsIContent* aDeckNode, nsIFrame* aPrevBoxFrame, nsIFrame* aCurrentBoxFrame); @@ -118,35 +136,35 @@ public: void ContentRangeInserted(nsIPresShell* aPresShell, nsIContent* aContainer, nsIContent* aStartChild, nsIContent* aEndChild); /** * Notification used to update the accessible tree when content is removed. */ void ContentRemoved(nsIPresShell* aPresShell, nsIContent* aChild); - virtual void UpdateText(nsIPresShell* aPresShell, nsIContent* aContent); + void UpdateText(nsIPresShell* aPresShell, nsIContent* aContent); /** * Update XUL:tree accessible tree when treeview is changed. */ void TreeViewChanged(nsIPresShell* aPresShell, nsIContent* aContent, nsITreeView* aView); /** * Notify of input@type="element" value change. */ void RangeValueChanged(nsIPresShell* aPresShell, nsIContent* aContent); /** * Update list bullet accessible. */ - virtual void UpdateListBullet(nsIPresShell* aPresShell, - nsIContent* aHTMLListItemContent, - bool aHasBullet); + void UpdateListBullet(nsIPresShell* aPresShell, + nsIContent* aHTMLListItemContent, + bool aHasBullet); /** * Update the image map. */ void UpdateImageMap(nsImageFrame* aImageFrame); /** * Update the label accessible tree when rendered @value is changed. @@ -158,33 +176,38 @@ public: * Notify accessibility that anchor jump has been accomplished to the given * target. Used by layout. */ void NotifyOfAnchorJumpTo(nsIContent *aTarget); /** * Notify that presshell is activated. */ - virtual void PresShellActivated(nsIPresShell* aPresShell); + void PresShellActivated(nsIPresShell* aPresShell); /** * Recreate an accessible for the given content node in the presshell. */ void RecreateAccessible(nsIPresShell* aPresShell, nsIContent* aContent); - virtual void FireAccessibleEvent(uint32_t aEvent, Accessible* aTarget) override; + void FireAccessibleEvent(uint32_t aEvent, Accessible* aTarget); // nsAccessibiltiyService /** * Return true if accessibility service has been shutdown. */ static bool IsShutdown() { return gIsShutdown; } /** + * Return true if accessibility service has been initialized by platform. + */ + static bool IsPlatformCaller() { return gIsPlatformCaller; }; + + /** * Creates an accessible for the given DOM node. * * @param aNode [in] the given node * @param aContext [in] context the accessible is created in * @param aIsSubtreeHidden [out, optional] indicates whether the node's * frame and its subtree is hidden */ Accessible* CreateAccessible(nsINode* aNode, Accessible* aContext, @@ -200,17 +223,17 @@ public: /** * Set the object attribute defined by markup for the given element. */ void MarkupAttributes(const nsIContent* aContent, nsIPersistentProperties* aAttributes) const; private: // nsAccessibilityService creation is controlled by friend - // NS_GetAccessibilityService, keep constructors private. + // GetOrCreateAccService, keep constructors private. nsAccessibilityService(); nsAccessibilityService(const nsAccessibilityService&); nsAccessibilityService& operator =(const nsAccessibilityService&); private: /** * Initialize accessibility service. */ @@ -253,53 +276,69 @@ private: static mozilla::a11y::ApplicationAccessible* gApplicationAccessible; static mozilla::a11y::xpcAccessibleApplication* gXPCApplicationAccessible; /** * Indicates whether accessibility service was shutdown. */ static bool gIsShutdown; + /** + * Indicates whether accessibility service was initialized by platform. + */ + static bool gIsPlatformCaller; + nsDataHashtable<nsPtrHashKey<const nsIAtom>, const mozilla::a11y::MarkupMapInfo*> mMarkupMaps; friend nsAccessibilityService* GetAccService(); + friend nsAccessibilityService* GetOrCreateAccService(bool); + friend bool CanShutdownAccService(); friend mozilla::a11y::FocusManager* mozilla::a11y::FocusMgr(); friend mozilla::a11y::SelectionManager* mozilla::a11y::SelectionMgr(); friend mozilla::a11y::ApplicationAccessible* mozilla::a11y::ApplicationAcc(); friend mozilla::a11y::xpcAccessibleApplication* mozilla::a11y::XPCApplicationAcc(); - - friend nsresult NS_GetAccessibilityService(nsIAccessibilityService** aResult); + friend class xpcAccessibilityService; }; /** * Return the accessibility service instance. (Handy global function) */ inline nsAccessibilityService* GetAccService() { return nsAccessibilityService::gAccessibilityService; } /** + * Return accessibility service instance; creating one if necessary. + */ +nsAccessibilityService* GetOrCreateAccService(bool aIsPlatformCaller = true); + +/** + * Return a flag indicating if accessibility service can be shutdown. + */ +bool CanShutdownAccService(); + +/** * Return true if we're in a content process and not B2G. */ inline bool IPCAccessibilityActive() { #ifdef MOZ_B2G return false; #else return XRE_IsContentProcess() && mozilla::Preferences::GetBool("accessibility.ipc_architecture.enabled", true); #endif } /** * Map nsIAccessibleEvents constants to strings. Used by - * nsIAccessibleRetrieval::getStringEventType() method. + * nsAccessibilityService::GetStringEventType() method. */ static const char kEventTypeNames[][40] = { "unknown", // "show", // EVENT_SHOW "hide", // EVENT_HIDE "reorder", // EVENT_REORDER "active decendent change", // EVENT_ACTIVE_DECENDENT_CHANGED "focus", // EVENT_FOCUS @@ -382,10 +421,9 @@ static const char kEventTypeNames[][40] "hyperlink start index changed", // EVENT_HYPERLINK_START_INDEX_CHANGED "hypertext changed", // EVENT_HYPERTEXT_CHANGED "hypertext links count changed", // EVENT_HYPERTEXT_NLINKS_CHANGED "object attribute changed", // EVENT_OBJECT_ATTRIBUTE_CHANGED "virtual cursor changed", // EVENT_VIRTUALCURSOR_CHANGED "text value change", // EVENT_TEXT_VALUE_CHANGE }; -#endif /* __nsIAccessibilityService_h__ */ - +#endif
--- a/accessible/interfaces/moz.build +++ b/accessible/interfaces/moz.build @@ -3,43 +3,38 @@ # 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/. if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows' and CONFIG['COMPILE_ENVIRONMENT']: DIRS += ['msaa', 'ia2'] XPIDL_SOURCES += [ + 'nsIAccessibilityService.idl', 'nsIAccessible.idl', 'nsIAccessibleApplication.idl', 'nsIAccessibleCaretMoveEvent.idl', 'nsIAccessibleDocument.idl', 'nsIAccessibleEditableText.idl', 'nsIAccessibleEvent.idl', 'nsIAccessibleHideEvent.idl', 'nsIAccessibleHyperLink.idl', 'nsIAccessibleHyperText.idl', 'nsIAccessibleImage.idl', 'nsIAccessibleObjectAttributeChangedEvent.idl', 'nsIAccessiblePivot.idl', 'nsIAccessibleRelation.idl', - 'nsIAccessibleRetrieval.idl', 'nsIAccessibleRole.idl', 'nsIAccessibleSelectable.idl', 'nsIAccessibleStateChangeEvent.idl', 'nsIAccessibleStates.idl', 'nsIAccessibleTable.idl', 'nsIAccessibleTableChangeEvent.idl', 'nsIAccessibleText.idl', 'nsIAccessibleTextChangeEvent.idl', 'nsIAccessibleTextRange.idl', 'nsIAccessibleTypes.idl', 'nsIAccessibleValue.idl', 'nsIAccessibleVirtualCursorChangeEvent.idl', 'nsIXBLAccessible.idl', ] XPIDL_MODULE = 'accessibility' - -EXPORTS += [ - 'nsIAccessibilityService.h', -] -
deleted file mode 100644 --- a/accessible/interfaces/nsIAccessibilityService.h +++ /dev/null @@ -1,79 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * - * 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 _nsIAccessibilityService_h_ -#define _nsIAccessibilityService_h_ - -#include "nsIAccessibleRetrieval.h" -#include "nsIAccessibleEvent.h" - -namespace mozilla { -namespace a11y { - -class Accessible; - -} // namespace a11y -} // namespace mozilla - -class nsIPresShell; - -// 0e7e6879-854b-4260-bc6e-525b5fb5cf34 -#define NS_IACCESSIBILITYSERVICE_IID \ -{ 0x0e7e6879, 0x854b, 0x4260, \ - { 0xbc, 0x6e, 0x52, 0x5b, 0x5f, 0xb5, 0xcf, 0x34 } } - -class nsIAccessibilityService : public nsIAccessibleRetrieval -{ -public: - NS_DECLARE_STATIC_IID_ACCESSOR(NS_IACCESSIBILITYSERVICE_IID) - - /** - * Return root document accessible that is or contains a document accessible - * for the given presshell. - * - * @param aPresShell [in] the presshell - * @param aCanCreate [in] points whether the root document accessible - * should be returned from the cache or can be created - */ - virtual mozilla::a11y::Accessible* - GetRootDocumentAccessible(nsIPresShell* aPresShell, bool aCanCreate) = 0; - - /** - * Adds/remove ATK root accessible for gtk+ native window to/from children - * of the application accessible. - */ - virtual mozilla::a11y::Accessible* - AddNativeRootAccessible(void* aAtkAccessible) = 0; - virtual void - RemoveNativeRootAccessible(mozilla::a11y::Accessible* aRootAccessible) = 0; - - /** - * Fire accessible event of the given type for the given target. - * - * @param aEvent [in] accessible event type - * @param aTarget [in] target of accessible event - */ - virtual void FireAccessibleEvent(uint32_t aEvent, - mozilla::a11y::Accessible* aTarget) = 0; - - /** - * Return true if the given DOM node has accessible object. - */ - virtual bool HasAccessible(nsIDOMNode* aDOMNode) = 0; -}; - -NS_DEFINE_STATIC_IID_ACCESSOR(nsIAccessibilityService, - NS_IACCESSIBILITYSERVICE_IID) - -// for component registration -// {DE401C37-9A7F-4278-A6F8-3DE2833989EF} -#define NS_ACCESSIBILITY_SERVICE_CID \ -{ 0xde401c37, 0x9a7f, 0x4278, { 0xa6, 0xf8, 0x3d, 0xe2, 0x83, 0x39, 0x89, 0xef } } - -extern nsresult -NS_GetAccessibilityService(nsIAccessibilityService** aResult); - -#endif
new file mode 100644 --- /dev/null +++ b/accessible/interfaces/nsIAccessibilityService.idl @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +/* 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 "nsISupports.idl" + +interface nsIDOMNode; +interface nsIAccessible; +interface nsIWeakReference; +interface nsIPresShell; +interface nsIAccessiblePivot; + +/** + * An interface for in-process accessibility clients wishing to get an + * nsIAccessible for a given DOM node. More documentation at: + * http://www.mozilla.org/projects/ui/accessibility + */ +[scriptable, builtinclass, uuid(9a6f80fe-25cc-405c-9f8f-25869bc9f94e)] +interface nsIAccessibilityService : nsISupports +{ + /** + * Return application accessible. + */ + nsIAccessible getApplicationAccessible(); + + /** + * Return an nsIAccessible for a DOM node in pres shell 0. + * Create a new accessible of the appropriate type if necessary, + * or use one from the accessibility cache if it already exists. + * @param aNode The DOM node to get an accessible for. + * @return The nsIAccessible for the given DOM node. + */ + nsIAccessible getAccessibleFor(in nsIDOMNode aNode); + + /** + * Returns accessible role as a string. + * + * @param aRole - the accessible role constants. + */ + AString getStringRole(in unsigned long aRole); + + /** + * Returns list which contains accessible states as a strings. + * + * @param aStates - accessible states. + * @param aExtraStates - accessible extra states. + */ + nsISupports getStringStates(in unsigned long aStates, + in unsigned long aExtraStates); + + /** + * Get the type of accessible event as a string. + * + * @param aEventType - the accessible event type constant + * @return - accessible event type presented as human readable string + */ + AString getStringEventType(in unsigned long aEventType); + + /** + * Get the type of accessible relation as a string. + * + * @param aRelationType - the accessible relation type constant + * @return - accessible relation type presented as human readable string + */ + AString getStringRelationType(in unsigned long aRelationType); + + /** + * Return an accessible for the given DOM node from the cache. + * @note the method is intended for testing purposes + * + * @param aNode [in] the DOM node to get an accessible for + * + * @return cached accessible for the given DOM node if any + */ + nsIAccessible getAccessibleFromCache(in nsIDOMNode aNode); + + /** + * Create a new pivot for tracking a position and traversing a subtree. + * + * @param aRoot [in] the accessible root for the pivot + * @return a new pivot + */ + nsIAccessiblePivot createAccessiblePivot(in nsIAccessible aRoot); + + /** + * Enable logging for the given modules, all other modules aren't logged. + * + * @param aModules [in] list of modules, format is comma separated list + * like 'docload,doccreate'. + * @note Works on debug build only. + * @see Logging.cpp for list of possible values. + */ + void setLogging(in ACString aModules); + + /** + * Return true if the given module is logged. + */ + boolean isLogged(in AString aModule); +}; + +/** + * @deprecated, use nsIAccessibilityService instead. + */ +[scriptable, builtinclass, uuid(d85e0cbe-47ce-490c-8488-f821dd2be0c2)] +interface nsIAccessibleRetrieval : nsIAccessibilityService +{ +};
deleted file mode 100644 --- a/accessible/interfaces/nsIAccessibleRetrieval.idl +++ /dev/null @@ -1,110 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- -/* 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 "nsISupports.idl" - -interface nsIDOMNode; -interface nsIAccessible; -interface nsIWeakReference; -interface nsIPresShell; -interface nsIAccessiblePivot; - -/** - * An interface for in-process accessibility clients wishing to get an - * nsIAccessible for a given DOM node. More documentation at: - * http://www.mozilla.org/projects/ui/accessibility - */ -[scriptable, builtinclass, uuid(17f86615-1a3d-4021-b227-3a2ef5cbffd8)] -interface nsIAccessibleRetrieval : nsISupports -{ - /** - * Return application accessible. - */ - nsIAccessible getApplicationAccessible(); - - /** - * Return an nsIAccessible for a DOM node in pres shell 0. - * Create a new accessible of the appropriate type if necessary, - * or use one from the accessibility cache if it already exists. - * @param aNode The DOM node to get an accessible for. - * @return The nsIAccessible for the given DOM node. - */ - nsIAccessible getAccessibleFor(in nsIDOMNode aNode); - - /** - * Returns accessible role as a string. - * - * @param aRole - the accessible role constants. - */ - AString getStringRole(in unsigned long aRole); - - /** - * Returns list which contains accessible states as a strings. - * - * @param aStates - accessible states. - * @param aExtraStates - accessible extra states. - */ - nsISupports getStringStates(in unsigned long aStates, - in unsigned long aExtraStates); - - /** - * Get the type of accessible event as a string. - * - * @param aEventType - the accessible event type constant - * @return - accessible event type presented as human readable string - */ - AString getStringEventType(in unsigned long aEventType); - - /** - * Get the type of accessible relation as a string. - * - * @param aRelationType - the accessible relation type constant - * @return - accessible relation type presented as human readable string - */ - AString getStringRelationType(in unsigned long aRelationType); - - /** - * Return an accessible for the given DOM node from the cache. - * @note the method is intended for testing purposes - * - * @param aNode [in] the DOM node to get an accessible for - * - * @return cached accessible for the given DOM node if any - */ - nsIAccessible getAccessibleFromCache(in nsIDOMNode aNode); - - /** - * Create a new pivot for tracking a position and traversing a subtree. - * - * @param aRoot [in] the accessible root for the pivot - * @return a new pivot - */ - nsIAccessiblePivot createAccessiblePivot(in nsIAccessible aRoot); - - /** - * Enable logging for the given modules, all other modules aren't logged. - * - * @param aModules [in] list of modules, format is comma separated list - * like 'docload,doccreate'. - * @note Works on debug build only. - * @see Logging.cpp for list of possible values. - */ - void setLogging(in ACString aModules); - - /** - * Return true if the given module is logged. - */ - boolean isLogged(in AString aModule); -}; - - -%{ C++ - -// for component registration -// {663CA4A8-D219-4000-925D-D8F66406B626} -#define NS_ACCESSIBLE_RETRIEVAL_CID \ -{ 0x663ca4a8, 0xd219, 0x4000, { 0x92, 0x5d, 0xd8, 0xf6, 0x64, 0x6, 0xb6, 0x26 } } - -%}
--- a/accessible/xpcom/moz.build +++ b/accessible/xpcom/moz.build @@ -1,16 +1,17 @@ # -*- 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/. UNIFIED_SOURCES += [ 'nsAccessibleRelation.cpp', + 'xpcAccessibilityService.cpp', 'xpcAccessible.cpp', 'xpcAccessibleApplication.cpp', 'xpcAccessibleDocument.cpp', 'xpcAccessibleGeneric.cpp', 'xpcAccessibleHyperLink.cpp', 'xpcAccessibleHyperText.cpp', 'xpcAccessibleImage.cpp', 'xpcAccessibleSelectable.cpp', @@ -21,16 +22,17 @@ UNIFIED_SOURCES += [ ] SOURCES += [ '!xpcAccEvents.cpp', ] EXPORTS += [ '!xpcAccEvents.h', + 'xpcAccessibilityService.h', ] LOCAL_INCLUDES += [ '/accessible/base', '/accessible/generic', ] if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
new file mode 100644 --- /dev/null +++ b/accessible/xpcom/xpcAccessibilityService.cpp @@ -0,0 +1,249 @@ +/* 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 "xpcAccessibilityService.h" + +#include "nsAccessiblePivot.h" +#include "nsAccessibilityService.h" + +#ifdef A11Y_LOG +#include "Logging.h" +#endif + +using namespace mozilla; +using namespace mozilla::a11y; +using namespace mozilla::dom; + +xpcAccessibilityService *xpcAccessibilityService::gXPCAccessibilityService = nullptr; + +//////////////////////////////////////////////////////////////////////////////// +// nsISupports + +void +xpcAccessibilityService::ShutdownCallback(nsITimer* aTimer, void* aClosure) +{ + if (CanShutdownAccService()) { + GetAccService()->Shutdown(); + } + + xpcAccessibilityService* xpcAccService = + reinterpret_cast<xpcAccessibilityService*>(aClosure); + + if (xpcAccService->mShutdownTimer) { + xpcAccService->mShutdownTimer->Cancel(); + xpcAccService->mShutdownTimer = nullptr; + } +} + +NS_IMETHODIMP_(MozExternalRefCountType) +xpcAccessibilityService::AddRef(void) +{ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(xpcAccessibilityService) + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + if (!mRefCnt.isThreadSafe) + NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService); + nsrefcnt count = ++mRefCnt; + NS_LOG_ADDREF(this, count, "xpcAccessibilityService", sizeof(*this)); + + if (mRefCnt > 1) { + GetOrCreateAccService(false); + } + + return count; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +xpcAccessibilityService::Release(void) +{ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + + if (!mRefCnt.isThreadSafe) { + NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService); + } + + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "xpcAccessibilityService"); + + if (count == 0) { + if (!mRefCnt.isThreadSafe) { + NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService); + } + + mRefCnt = 1; /* stabilize */ + delete (this); + return 0; + } + + // When ref count goes down to 1 (held internally as a static reference), + // it means that there are no more external references to the + // xpcAccessibilityService and we can attempt to shut down acceessiblity + // service. + if (count == 1 && !mShutdownTimer) { + mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (mShutdownTimer) { + mShutdownTimer->InitWithFuncCallback(ShutdownCallback, this, 100, + nsITimer::TYPE_ONE_SHOT); + } + } + + return count; +} + +NS_IMPL_QUERY_INTERFACE(xpcAccessibilityService, nsIAccessibilityService, + nsIAccessibleRetrieval) + +NS_IMETHODIMP +xpcAccessibilityService::GetApplicationAccessible(nsIAccessible** aAccessibleApplication) +{ + NS_ENSURE_ARG_POINTER(aAccessibleApplication); + + NS_IF_ADDREF(*aAccessibleApplication = XPCApplicationAcc()); + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibilityService::GetAccessibleFor(nsIDOMNode *aNode, + nsIAccessible **aAccessible) +{ + NS_ENSURE_ARG_POINTER(aAccessible); + *aAccessible = nullptr; + if (!aNode) { + return NS_OK; + } + + nsCOMPtr<nsINode> node(do_QueryInterface(aNode)); + if (!node) { + return NS_ERROR_INVALID_ARG; + } + + DocAccessible* document = GetAccService()->GetDocAccessible(node->OwnerDoc()); + if (document) { + NS_IF_ADDREF(*aAccessible = ToXPC(document->GetAccessible(node))); + } + + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibilityService::GetStringRole(uint32_t aRole, nsAString& aString) +{ + GetAccService()->GetStringRole(aRole, aString); + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibilityService::GetStringStates(uint32_t aState, uint32_t aExtraState, + nsISupports **aStringStates) +{ + GetAccService()->GetStringStates(aState, aExtraState, aStringStates); + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibilityService::GetStringEventType(uint32_t aEventType, + nsAString& aString) +{ + GetAccService()->GetStringEventType(aEventType, aString); + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibilityService::GetStringRelationType(uint32_t aRelationType, + nsAString& aString) +{ + GetAccService()->GetStringRelationType(aRelationType, aString); + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibilityService::GetAccessibleFromCache(nsIDOMNode* aNode, + nsIAccessible** aAccessible) +{ + NS_ENSURE_ARG_POINTER(aAccessible); + *aAccessible = nullptr; + if (!aNode) { + return NS_OK; + } + + nsCOMPtr<nsINode> node(do_QueryInterface(aNode)); + if (!node) { + return NS_ERROR_INVALID_ARG; + } + + // Search for an accessible in each of our per document accessible object + // caches. If we don't find it, and the given node is itself a document, check + // our cache of document accessibles (document cache). Note usually shutdown + // document accessibles are not stored in the document cache, however an + // "unofficially" shutdown document (i.e. not from DocManager) can still + // exist in the document cache. + Accessible* accessible = GetAccService()->FindAccessibleInCache(node); + if (!accessible) { + nsCOMPtr<nsIDocument> document(do_QueryInterface(node)); + if (document) { + accessible = mozilla::a11y::GetExistingDocAccessible(document); + } + } + + NS_IF_ADDREF(*aAccessible = ToXPC(accessible)); + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibilityService::CreateAccessiblePivot(nsIAccessible* aRoot, + nsIAccessiblePivot** aPivot) +{ + NS_ENSURE_ARG_POINTER(aPivot); + NS_ENSURE_ARG(aRoot); + *aPivot = nullptr; + + Accessible* accessibleRoot = aRoot->ToInternalAccessible(); + NS_ENSURE_TRUE(accessibleRoot, NS_ERROR_INVALID_ARG); + + nsAccessiblePivot* pivot = new nsAccessiblePivot(accessibleRoot); + NS_ADDREF(*aPivot = pivot); + + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibilityService::SetLogging(const nsACString& aModules) +{ +#ifdef A11Y_LOG + logging::Enable(PromiseFlatCString(aModules)); +#endif + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibilityService::IsLogged(const nsAString& aModule, bool* aIsLogged) +{ + NS_ENSURE_ARG_POINTER(aIsLogged); + *aIsLogged = false; + +#ifdef A11Y_LOG + *aIsLogged = logging::IsEnabled(aModule); +#endif + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// NS_GetAccessibilityService +//////////////////////////////////////////////////////////////////////////////// + +nsresult +NS_GetAccessibilityService(nsIAccessibilityService** aResult) +{ + NS_ENSURE_TRUE(aResult, NS_ERROR_NULL_POINTER); + *aResult = nullptr; + + GetOrCreateAccService(false); + + xpcAccessibilityService* service = new xpcAccessibilityService(); + NS_ENSURE_TRUE(service, NS_ERROR_OUT_OF_MEMORY); + xpcAccessibilityService::gXPCAccessibilityService = service; + NS_ADDREF(*aResult = service); + + return NS_OK; +}
new file mode 100644 --- /dev/null +++ b/accessible/xpcom/xpcAccessibilityService.h @@ -0,0 +1,66 @@ +/* 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_a11y_xpcAccessibilityService_h_ +#define mozilla_a11y_xpcAccessibilityService_h_ + +#include "nsIAccessibilityService.h" + +class xpcAccessibilityService : public nsIAccessibleRetrieval +{ + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIACCESSIBILITYSERVICE + NS_DECL_NSIACCESSIBLERETRIEVAL + + /** + * Return true if xpc accessibility service is in use. + */ + static bool IsInUse() { + // When ref count goes down to 1 (held internally as a static reference), + // it means that there are no more external references and thus it is not in + // use. + return gXPCAccessibilityService ? gXPCAccessibilityService->mRefCnt > 1 : false; + } + +protected: + virtual ~xpcAccessibilityService() { + if (mShutdownTimer) { + mShutdownTimer->Cancel(); + mShutdownTimer = nullptr; + } + gXPCAccessibilityService = nullptr; + } + +private: + // xpcAccessibilityService creation is controlled by friend + // NS_GetAccessibilityService, keep constructor private. + xpcAccessibilityService() { }; + + nsCOMPtr<nsITimer> mShutdownTimer; + + /** + * Reference for xpc accessibility service instance. + */ + static xpcAccessibilityService* gXPCAccessibilityService; + + /** + * Used to shutdown nsAccessibilityService if xpcom accessible service is not + * in use any more. + */ + static void ShutdownCallback(nsITimer* aTimer, void* aClosure); + + friend nsresult NS_GetAccessibilityService(nsIAccessibilityService** aResult); +}; + +// for component registration +// {3b265b69-f813-48ff-880d-d88d101af404} +#define NS_ACCESSIBILITY_SERVICE_CID \ +{ 0x3b265b69, 0xf813, 0x48ff, { 0x88, 0x0d, 0xd8, 0x8d, 0x10, 0x1a, 0xf4, 0x04 } } + +extern nsresult +NS_GetAccessibilityService(nsIAccessibilityService** aResult); + +#endif
--- a/browser/components/contextualidentity/test/browser/browser.ini +++ b/browser/components/contextualidentity/test/browser/browser.ini @@ -19,8 +19,9 @@ skip-if = os == "mac" || os == "win" # I [browser_windowName.js] tags = openwindow [browser_windowOpen.js] tags = openwindow [browser_serviceworkers.js] [browser_broadcastchannel.js] [browser_blobUrl.js] [browser_middleClick.js] +[browser_imageCache.js]
--- a/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js +++ b/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js @@ -10,17 +10,16 @@ let {HttpServer} = Cu.import("resource:/ let LoadContextInfo = Cc["@mozilla.org/load-context-info-factory;1"] .getService(Ci.nsILoadContextInfoFactory); let css = Cc["@mozilla.org/netwerk/cache-storage-service;1"] .getService(Ci.nsICacheStorageService); const USER_CONTEXTS = [ "default", "personal", - "work", ]; const TEST_HOST = "example.com"; const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/"; const COOKIE_NAME = "userContextId"; // Counter for image load hits. let gHits = 0; @@ -128,17 +127,17 @@ function OpenCacheEntry(key, where, flag // Test functions. // // Cookies function* test_cookie_cleared() { let tabs = []; for (let userContextId of Object.keys(USER_CONTEXTS)) { - // Load the page in 3 different contexts and set a cookie + // Load the page in 2 different contexts and set a cookie // which should only be visible in that context. let value = USER_CONTEXTS[userContextId]; // Open our tab in the given user context. tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "file_reflect_cookie_into_title.html?" + value, userContextId); // Close this tab. yield BrowserTestUtils.removeTab(tabs[userContextId].tab); @@ -207,41 +206,43 @@ function* test_image_cache_cleared() { for (let userContextId of Object.keys(USER_CONTEXTS)) { // Open our tab in the given user context to cache image. tabs[userContextId] = yield* openTabInUserContext('http://localhost:' + gHttpServer.identity.primaryPort + '/loadImage.html', userContextId); yield BrowserTestUtils.removeTab(tabs[userContextId].tab); } + let expectedHits = USER_CONTEXTS.length; + // Check that image cache works with the userContextId. - todo_is(gHits, 3, "The image should be loaded three times. This test should be enabled after the bug 1270680 landed"); + is(gHits, expectedHits, "The image should be loaded" + expectedHits + "times."); // Reset the cache count. gHits = 0; // Forget the site. ForgetAboutSite.removeDataFromDomain("localhost:" + gHttpServer.identity.primaryPort + "/"); // Load again. for (let userContextId of Object.keys(USER_CONTEXTS)) { // Open our tab in the given user context to cache image. tabs[userContextId] = yield* openTabInUserContext('http://localhost:' + gHttpServer.identity.primaryPort + '/loadImage.html', userContextId); yield BrowserTestUtils.removeTab(tabs[userContextId].tab); } - // Check that image cache was cleared and the server gets another three hits. - todo_is(gHits, 3, "The image should be loaded three times. This test should be enabled after the bug 1270680 landed"); + // Check that image cache was cleared and the server gets another two hits. + is(gHits, expectedHits, "The image should be loaded" + expectedHits + "times."); } // Offline Storage function* test_storage_cleared() { for (let userContextId of Object.keys(USER_CONTEXTS)) { - // Load the page in 3 different contexts and set the local storage + // Load the page in 2 different contexts and set the local storage // which should only be visible in that context. let value = USER_CONTEXTS[userContextId]; // Open our tab in the given user context. let tabInfo = yield* openTabInUserContext(TEST_URL+ "file_set_storages.html?" + value, userContextId); // Check that the storages has been set correctly. yield ContentTask.spawn(tabInfo.browser, { userContext: USER_CONTEXTS[userContextId] }, function* (arg) {
new file mode 100644 --- /dev/null +++ b/browser/components/contextualidentity/test/browser/browser_imageCache.js @@ -0,0 +1,59 @@ +let Cu = Components.utils; +let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {}); + +const NUM_USER_CONTEXTS = 3; + +let gHits = 0; + +let server = new HttpServer(); +server.registerPathHandler('/image.png', imageHandler); +server.registerPathHandler('/file.html', fileHandler); +server.start(-1); + +let BASE_URI = 'http://localhost:' + server.identity.primaryPort; +let IMAGE_URI = BASE_URI + '/image.png'; +let FILE_URI = BASE_URI + '/file.html'; + +function imageHandler(metadata, response) { + gHits++; + response.setHeader("Cache-Control", "max-age=10000", false); + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "image/png", false); + var body = "iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII="; + response.bodyOutputStream.write(body, body.length); +} + +function fileHandler(metadata, response) { + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html", false); + let body = `<html><body><image src=${IMAGE_URI}></body></html>`; + response.bodyOutputStream.write(body, body.length); +} + +add_task(function* setup() { + // make sure userContext is enabled. + yield SpecialPowers.pushPrefEnv({"set": [["privacy.userContext.enabled", true]]}); +}); + +// opens `uri' in a new tab with the provided userContextId and focuses it. +// returns the newly opened tab +function* openTabInUserContext(uri, userContextId) { + // open the tab in the correct userContextId + let tab = gBrowser.addTab(uri, {userContextId}); + + // select tab and make sure its browser is focused + gBrowser.selectedTab = tab; + tab.ownerDocument.defaultView.focus(); + + let browser = gBrowser.getBrowserForTab(tab); + yield BrowserTestUtils.browserLoaded(browser); + return tab; +} + +add_task(function* test() { + for (let userContextId = 0; userContextId < NUM_USER_CONTEXTS; userContextId++) { + let tab = yield* openTabInUserContext(FILE_URI, userContextId); + gBrowser.removeTab(tab); + } + is(gHits, NUM_USER_CONTEXTS, "should get an image request for each user contexts"); +});
--- a/browser/components/extensions/ext-utils.js +++ b/browser/components/extensions/ext-utils.js @@ -211,24 +211,28 @@ class BasePopup { this.browser.addEventListener("DOMTitleChanged", this, true); this.browser.addEventListener("DOMWindowClose", this, true); this.browser.addEventListener("MozScrolledAreaChanged", this, true); }); } // Resizes the browser to match the preferred size of the content (debounced). resizeBrowser() { if (this.resizeTimeout == null) { - this._resizeBrowser(); - this.resizeTimeout = this.window.setTimeout(this._resizeBrowser.bind(this), RESIZE_TIMEOUT); + this.resizeTimeout = this.window.setTimeout(() => { + try { + this._resizeBrowser(); + } finally { + this.resizeTimeout = null; + } + }, RESIZE_TIMEOUT); + this._resizeBrowser(false); } } - _resizeBrowser() { - this.resizeTimeout = null; - + _resizeBrowser(clearTimeout = true) { if (!this.browser) { return; } if (this.fixedWidth) { // If we're in a fixed-width area (namely a slide-in subview of the main // menu panel), we need to calculate the view height based on the // preferred height of the content document's root scrollable element at the
--- a/browser/components/extensions/schemas/browser_action.json +++ b/browser/components/extensions/schemas/browser_action.json @@ -37,16 +37,17 @@ } } } ] }, { "namespace": "browserAction", "description": "Use browser actions to put icons in the main browser toolbar, to the right of the address bar. In addition to its icon, a browser action can also have a tooltip, a badge, and a popup.", + "permissions": ["manifest:browser_action"], "types": [ { "id": "ColorArray", "type": "array", "items": { "type": "integer", "minimum": 0, "maximum": 255
--- a/browser/components/extensions/schemas/page_action.json +++ b/browser/components/extensions/schemas/page_action.json @@ -37,16 +37,17 @@ } } } ] }, { "namespace": "pageAction", "description": "Use the <code>browser.pageAction</code> API to put icons inside the address bar. Page actions represent actions that can be taken on the current page, but that aren't applicable to all pages.", + "permissions": ["manifest:page_action"], "types": [ { "id": "ImageDataType", "type": "object", "isInstanceOf": "ImageData", "additionalProperties": { "type": "any" }, "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)." }
--- a/browser/components/extensions/schemas/windows.json +++ b/browser/components/extensions/schemas/windows.json @@ -1,28 +1,14 @@ // Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. [ { - "namespace": "manifest", - "types": [ - { - "$extend": "Permission", - "choices": [{ - "type": "string", - "enum": [ - "windows" - ] - }] - } - ] - }, - { "namespace": "windows", "description": "Use the <code>browser.windows</code> API to interact with browser windows. You can use this API to create, modify, and rearrange windows in the browser.", "types": [ { "id": "WindowType", "type": "string", "description": "The type of browser window this is. Under some circumstances a Window may not be assigned type property, for example when querying closed windows from the $(ref:sessions) API.", "enum": ["normal", "popup", "panel", "app", "devtools"]
--- a/browser/components/extensions/test/browser/browser_ext_tabs_sendMessage.js +++ b/browser/components/extensions/test/browser/browser_ext_tabs_sendMessage.js @@ -16,42 +16,41 @@ add_task(function* tabsSendMessageReply( background: function() { let firstTab; let promiseResponse = new Promise(resolve => { browser.runtime.onMessage.addListener((msg, sender, respond) => { if (msg == "content-script-ready") { let tabId = sender.tab.id; - browser.tabs.sendMessage(tabId, "respond-never", response => { - browser.test.fail(`Got unexpected response callback: ${response}`); - browser.test.notifyFail("sendMessage"); - }); - Promise.all([ promiseResponse, browser.tabs.sendMessage(tabId, "respond-now"), browser.tabs.sendMessage(tabId, "respond-now-2"), new Promise(resolve => browser.tabs.sendMessage(tabId, "respond-soon", resolve)), browser.tabs.sendMessage(tabId, "respond-promise"), browser.tabs.sendMessage(tabId, "respond-never"), + new Promise(resolve => { + browser.runtime.sendMessage("respond-never", response => { resolve(response); }); + }), browser.tabs.sendMessage(tabId, "respond-error").catch(error => Promise.resolve({error})), browser.tabs.sendMessage(tabId, "throw-error").catch(error => Promise.resolve({error})), browser.tabs.sendMessage(firstTab, "no-listener").catch(error => Promise.resolve({error})), - ]).then(([response, respondNow, respondNow2, respondSoon, respondPromise, respondNever, respondError, throwError, noListener]) => { + ]).then(([response, respondNow, respondNow2, respondSoon, respondPromise, respondNever, respondNever2, respondError, throwError, noListener]) => { browser.test.assertEq("expected-response", response, "Content script got the expected response"); browser.test.assertEq("respond-now", respondNow, "Got the expected immediate response"); browser.test.assertEq("respond-now-2", respondNow2, "Got the expected immediate response from the second listener"); browser.test.assertEq("respond-soon", respondSoon, "Got the expected delayed response"); browser.test.assertEq("respond-promise", respondPromise, "Got the expected promise response"); browser.test.assertEq(undefined, respondNever, "Got the expected no-response resolution"); + browser.test.assertEq(undefined, respondNever2, "Got the expected no-response resolution"); browser.test.assertEq("respond-error", respondError.error.message, "Got the expected error response"); browser.test.assertEq("throw-error", throwError.error.message, "Got the expected thrown error response"); browser.test.assertEq("Could not establish connection. Receiving end does not exist.", noListener.error.message, "Got the expected no listener response");
new file mode 100644 --- /dev/null +++ b/browser/components/extensions/test/xpcshell/test_ext_manifest_permissions.js @@ -0,0 +1,57 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* globals chrome */ + +function* testPermission(options) { + function background(options) { + browser.test.sendMessage("typeof-namespace", { + browser: typeof browser[options.namespace], + chrome: typeof chrome[options.namespace], + }); + } + + let extensionDetails = { + background: `(${background})(${JSON.stringify(options)})`, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionDetails); + + yield extension.startup(); + + let types = yield extension.awaitMessage("typeof-namespace"); + equal(types.browser, "undefined", `Type of browser.${options.namespace} without manifest entry`); + equal(types.chrome, "undefined", `Type of chrome.${options.namespace} without manifest entry`); + + yield extension.unload(); + + extensionDetails.manifest = options.manifest; + extension = ExtensionTestUtils.loadExtension(extensionDetails); + + yield extension.startup(); + + types = yield extension.awaitMessage("typeof-namespace"); + equal(types.browser, "object", `Type of browser.${options.namespace} with manifest entry`); + equal(types.chrome, "object", `Type of chrome.${options.namespace} with manifest entry`); + + yield extension.unload(); +} + +add_task(function* test_browserAction() { + yield testPermission({ + namespace: "browserAction", + manifest: { + browser_action: {}, + }, + }); +}); + +add_task(function* test_pageAction() { + yield testPermission({ + namespace: "pageAction", + manifest: { + page_action: {}, + }, + }); +});
--- a/browser/components/extensions/test/xpcshell/xpcshell.ini +++ b/browser/components/extensions/test/xpcshell/xpcshell.ini @@ -1,8 +1,9 @@ [DEFAULT] head = head.js tail = firefox-appdir = browser [test_ext_bookmarks.js] [test_ext_history.js] [test_ext_manifest_commands.js] +[test_ext_manifest_permissions.js]
--- a/browser/themes/linux/browser.css +++ b/browser/themes/linux/browser.css @@ -1698,22 +1698,22 @@ toolbarbutton.chevron > .toolbarbutton-m toolbarbutton.chevron > .toolbarbutton-icon { margin: 0; } /* Ctrl-Tab */ #ctrlTab-panel { -moz-appearance: none; - background: rgba(27%,27%,27%,.7); + background: hsla(0,0%,33%,.85); color: white; border-style: none; padding: 20px 10px 10px; font-weight: bold; - text-shadow: 0 0 1px rgb(27%,27%,27%), 0 0 2px rgb(27%,27%,27%); + text-shadow: 0 0 1px hsl(0,0%,12%), 0 0 2px hsl(0,0%,12%); } .ctrlTab-favicon[src] { background-color: white; width: 20px; height: 20px; padding: 2px; }
deleted file mode 100644 --- a/browser/themes/linux/content-contextmenu.svg +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0"?> -<!-- 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/. --> -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"> - <style> - use:not(:target) { - display: none; - } - use { - fill: menutext; - } - use[id$="-active"] { - fill: -moz-menuhovertext; - } - use[id$="-disabled"] { - fill: graytext; - } - </style> - <defs> - <path id="back-shape" fill-rule="evenodd" d="M1.192,8.893L2.21,9.964c0.064,0.065,0.136,0.117,0.214,0.159 l5.199,5.301c0.607,0.63,1.465,0.764,1.915,0.297l1.02-1.082c0.449-0.467,0.32-1.357-0.288-1.99l-2.116-2.158h5.705 c0.671,0,1.215-0.544,1.215-1.215v-2.43c0-0.671-0.544-1.215-1.215-1.215H8.094l2.271-2.309c0.609-0.626,0.737-1.512,0.288-1.974 L9.635,0.278C9.184-0.188,8.327-0.055,7.718,0.575L2.479,5.901C2.38,5.946,2.289,6.008,2.21,6.089L1.192,7.171 c-0.21,0.219-0.293,0.53-0.26,0.864C0.899,8.367,0.981,8.676,1.192,8.893z"/> - <path id="forward-shape" fill-rule="evenodd" d="M14.808,7.107L13.79,6.036c-0.064-0.065-0.136-0.117-0.214-0.159 L8.377,0.576C7.77-0.054,6.912-0.189,6.461,0.278L5.441,1.36c-0.449,0.467-0.32,1.357,0.288,1.99l2.116,2.158H2.14 c-0.671,0-1.215,0.544-1.215,1.215v2.43c0,0.671,0.544,1.215,1.215,1.215h5.765l-2.271,2.309c-0.609,0.626-0.737,1.512-0.288,1.974 l1.019,1.072c0.451,0.465,1.308,0.332,1.917-0.297l5.238-5.326c0.1-0.045,0.191-0.107,0.269-0.188l1.019-1.082 c0.21-0.219,0.293-0.53,0.26-0.864C15.101,7.633,15.019,7.324,14.808,7.107z"/> - <path id="reload-shape" fill-rule="evenodd" d="M15.429,8h-8l3.207-3.207C9.889,4.265,8.986,3.947,8,3.947 c-2.554,0-4.625,2.071-4.625,4.625S5.446,13.196,8,13.196c1.638,0,3.069-0.857,3.891-2.141l2.576,1.104 C13.199,14.439,10.794,16,8,16c-4.103,0-7.429-3.326-7.429-7.429S3.897,1.143,8,1.143c1.762,0,3.366,0.624,4.631,1.654L15.429,0V8z"/> - <polygon id="stop-shape" points="16,2.748 13.338,0.079 8.038,5.391 2.661,0 0,2.669 5.377,8.059 0.157,13.292 2.819,15.961 8.039,10.728 13.298,16 15.959,13.331 10.701,8.06"/> - <path id="bookmark-shape" d="M8.008,3.632l0.986,2.012l0.452,0.922l1.014,0.169l2.326,0.389l-1.719,1.799l-0.676,0.708l0.145,0.967 L10.896,13l-1.959-1.039l-0.937-0.497l-0.937,0.497l-1.957,1.038L5.468,10.6l0.146-0.968L4.937,8.924L3.219,7.126l2.351-0.39 l1.023-0.17l0.45-0.934L8.008,3.632 M8,0C7.72,0,7.44,0.217,7.228,0.65L5.242,4.766L0.907,5.485c-0.958,0.159-1.195,0.861-0.53,1.56 l3.113,3.258l-0.69,4.583c-0.105,0.689,0.172,1.092,0.658,1.092c0.185,0,0.399-0.058,0.635-0.181l3.906-2.072l3.906,2.072 c0.236,0.123,0.45,0.181,0.635,0.181c0.486,0,0.762-0.403,0.659-1.092l-0.687-4.583l3.109-3.255c0.666-0.702,0.428-1.404-0.53-1.564 l-4.303-0.719L8.772,0.65C8.56,0.217,8.28,0,8,0L8,0z"/> - <path id="bookmarked-shape" d="M8,0C7.719,0,7.438,0.217,7.225,0.651L5.233,4.773l-4.35,0.72c-0.961,0.159-1.199,0.862-0.531,1.562 l3.124,3.262l-0.692,4.589C2.679,15.596,2.957,16,3.444,16c0.185,0,0.401-0.058,0.637-0.181L8,13.744l3.919,2.075 C12.156,15.942,12.372,16,12.557,16c0.487,0,0.764-0.404,0.661-1.094l-0.69-4.589l3.12-3.259c0.668-0.703,0.43-1.406-0.532-1.566 l-4.317-0.72L8.775,0.651C8.562,0.217,8.281,0,8,0L8,0z"/> - </defs> - <use id="back" xlink:href="#back-shape"/> - <use id="back-active" xlink:href="#back-shape"/> - <use id="back-disabled" xlink:href="#back-shape"/> - <use id="forward" xlink:href="#forward-shape"/> - <use id="forward-active" xlink:href="#forward-shape"/> - <use id="forward-disabled" xlink:href="#forward-shape"/> - <use id="reload" xlink:href="#reload-shape"/> - <use id="reload-active" xlink:href="#reload-shape"/> - <use id="reload-disabled" xlink:href="#reload-shape"/> - <use id="stop" xlink:href="#stop-shape"/> - <use id="stop-active" xlink:href="#stop-shape"/> - <use id="stop-disabled" xlink:href="#stop-shape"/> - <use id="bookmark" xlink:href="#bookmark-shape"/> - <use id="bookmark-active" xlink:href="#bookmark-shape"/> - <use id="bookmark-disabled" xlink:href="#bookmark-shape"/> - <use id="bookmarked" xlink:href="#bookmarked-shape"/> - <use id="bookmarked-active" xlink:href="#bookmarked-shape"/> - <use id="bookmarked-disabled" xlink:href="#bookmarked-shape"/> -</svg>
--- a/browser/themes/linux/customizableui/panelUI.css +++ b/browser/themes/linux/customizableui/panelUI.css @@ -1,16 +1,16 @@ /* 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 ../../shared/customizableui/panelUI.inc.css .panel-subviews { - background-color: -moz-dialog; + background-color: var(--panel-arrowcontent-background); } #BMB_bookmarksPopup > menuitem[type="checkbox"] { -moz-appearance: none !important; /* important, to override toolkit rule */ } #BMB_bookmarksPopup menupopup { -moz-appearance: none;
--- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -10,17 +10,16 @@ browser.jar: skin/classic/browser/aboutSessionRestore-window-icon.png skin/classic/browser/aboutSyncTabs.css * skin/classic/browser/syncedtabs/sidebar.css (syncedtabs/sidebar.css) skin/classic/browser/actionicon-tab.png * skin/classic/browser/browser.css * skin/classic/browser/devedition.css * skin/classic/browser/browser-lightweightTheme.css skin/classic/browser/click-to-play-warning-stripes.png - skin/classic/browser/content-contextmenu.svg skin/classic/browser/Info.png skin/classic/browser/menuPanel.png skin/classic/browser/menuPanel@2x.png skin/classic/browser/menuPanel-customize.png skin/classic/browser/menuPanel-customize@2x.png skin/classic/browser/menuPanel-exit.png skin/classic/browser/menuPanel-exit@2x.png skin/classic/browser/menuPanel-help.png
--- a/browser/themes/osx/browser.css +++ b/browser/themes/osx/browser.css @@ -3111,21 +3111,21 @@ menulist.translate-infobar-element > .me font-weight: bold; } /* Ctrl-Tab */ #ctrlTab-panel { -moz-appearance: none; -moz-window-shadow: none; - background: rgba(27%,27%,27%,.7); + background: hsla(0,0%,33%,.85); color: white; border-style: none; padding: 20px 10px 10px; - text-shadow: 0 0 1px rgb(27%,27%,27%), 0 0 2px rgb(27%,27%,27%); + text-shadow: 0 0 1px hsl(0,0%,12%), 0 0 2px hsl(0,0%,12%); } .ctrlTab-favicon[src] { background-color: white; width: 20px; height: 20px; padding: 2px; }
deleted file mode 100644 --- a/browser/themes/osx/content-contextmenu.svg +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0"?> -<!-- 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/. --> -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"> - <style> - use:not(:target) { - display: none; - } - use { - fill: menutext; - } - use[id$="-active"] { - fill: -moz-mac-menutextselect; - } - use[id$="-disabled"] { - fill: -moz-mac-menutextdisable; - } - </style> - <defs> - <path id="back-shape" fill-rule="evenodd" d="M1.192,8.893L2.21,9.964c0.064,0.065,0.136,0.117,0.214,0.159 l5.199,5.301c0.607,0.63,1.465,0.764,1.915,0.297l1.02-1.082c0.449-0.467,0.32-1.357-0.288-1.99l-2.116-2.158h5.705 c0.671,0,1.215-0.544,1.215-1.215v-2.43c0-0.671-0.544-1.215-1.215-1.215H8.094l2.271-2.309c0.609-0.626,0.737-1.512,0.288-1.974 L9.635,0.278C9.184-0.188,8.327-0.055,7.718,0.575L2.479,5.901C2.38,5.946,2.289,6.008,2.21,6.089L1.192,7.171 c-0.21,0.219-0.293,0.53-0.26,0.864C0.899,8.367,0.981,8.676,1.192,8.893z"/> - <path id="forward-shape" fill-rule="evenodd" d="M14.808,7.107L13.79,6.036c-0.064-0.065-0.136-0.117-0.214-0.159 L8.377,0.576C7.77-0.054,6.912-0.189,6.461,0.278L5.441,1.36c-0.449,0.467-0.32,1.357,0.288,1.99l2.116,2.158H2.14 c-0.671,0-1.215,0.544-1.215,1.215v2.43c0,0.671,0.544,1.215,1.215,1.215h5.765l-2.271,2.309c-0.609,0.626-0.737,1.512-0.288,1.974 l1.019,1.072c0.451,0.465,1.308,0.332,1.917-0.297l5.238-5.326c0.1-0.045,0.191-0.107,0.269-0.188l1.019-1.082 c0.21-0.219,0.293-0.53,0.26-0.864C15.101,7.633,15.019,7.324,14.808,7.107z"/> - <path id="reload-shape" fill-rule="evenodd" d="M15.429,8h-8l3.207-3.207C9.889,4.265,8.986,3.947,8,3.947 c-2.554,0-4.625,2.071-4.625,4.625S5.446,13.196,8,13.196c1.638,0,3.069-0.857,3.891-2.141l2.576,1.104 C13.199,14.439,10.794,16,8,16c-4.103,0-7.429-3.326-7.429-7.429S3.897,1.143,8,1.143c1.762,0,3.366,0.624,4.631,1.654L15.429,0V8z"/> - <polygon id="stop-shape" points="16,2.748 13.338,0.079 8.038,5.391 2.661,0 0,2.669 5.377,8.059 0.157,13.292 2.819,15.961 8.039,10.728 13.298,16 15.959,13.331 10.701,8.06"/> - <path id="bookmark-shape" d="M8.008,3.632l0.986,2.012l0.452,0.922l1.014,0.169l2.326,0.389l-1.719,1.799l-0.676,0.708l0.145,0.967 L10.896,13l-1.959-1.039l-0.937-0.497l-0.937,0.497l-1.957,1.038L5.468,10.6l0.146-0.968L4.937,8.924L3.219,7.126l2.351-0.39 l1.023-0.17l0.45-0.934L8.008,3.632 M8,0C7.72,0,7.44,0.217,7.228,0.65L5.242,4.766L0.907,5.485c-0.958,0.159-1.195,0.861-0.53,1.56 l3.113,3.258l-0.69,4.583c-0.105,0.689,0.172,1.092,0.658,1.092c0.185,0,0.399-0.058,0.635-0.181l3.906-2.072l3.906,2.072 c0.236,0.123,0.45,0.181,0.635,0.181c0.486,0,0.762-0.403,0.659-1.092l-0.687-4.583l3.109-3.255c0.666-0.702,0.428-1.404-0.53-1.564 l-4.303-0.719L8.772,0.65C8.56,0.217,8.28,0,8,0L8,0z"/> - <path id="bookmarked-shape" d="M8,0C7.719,0,7.438,0.217,7.225,0.651L5.233,4.773l-4.35,0.72c-0.961,0.159-1.199,0.862-0.531,1.562 l3.124,3.262l-0.692,4.589C2.679,15.596,2.957,16,3.444,16c0.185,0,0.401-0.058,0.637-0.181L8,13.744l3.919,2.075 C12.156,15.942,12.372,16,12.557,16c0.487,0,0.764-0.404,0.661-1.094l-0.69-4.589l3.12-3.259c0.668-0.703,0.43-1.406-0.532-1.566 l-4.317-0.72L8.775,0.651C8.562,0.217,8.281,0,8,0L8,0z"/> - </defs> - <use id="back" xlink:href="#back-shape"/> - <use id="back-active" xlink:href="#back-shape"/> - <use id="back-disabled" xlink:href="#back-shape"/> - <use id="forward" xlink:href="#forward-shape"/> - <use id="forward-active" xlink:href="#forward-shape"/> - <use id="forward-disabled" xlink:href="#forward-shape"/> - <use id="reload" xlink:href="#reload-shape"/> - <use id="reload-active" xlink:href="#reload-shape"/> - <use id="reload-disabled" xlink:href="#reload-shape"/> - <use id="stop" xlink:href="#stop-shape"/> - <use id="stop-active" xlink:href="#stop-shape"/> - <use id="stop-disabled" xlink:href="#stop-shape"/> - <use id="bookmark" xlink:href="#bookmark-shape"/> - <use id="bookmark-active" xlink:href="#bookmark-shape"/> - <use id="bookmark-disabled" xlink:href="#bookmark-shape"/> - <use id="bookmarked" xlink:href="#bookmarked-shape"/> - <use id="bookmarked-active" xlink:href="#bookmarked-shape"/> - <use id="bookmarked-disabled" xlink:href="#bookmarked-shape"/> -</svg>
--- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -10,17 +10,16 @@ browser.jar: skin/classic/browser/aboutSyncTabs.css * skin/classic/browser/syncedtabs/sidebar.css (syncedtabs/sidebar.css) skin/classic/browser/actionicon-tab.png skin/classic/browser/actionicon-tab@2x.png * skin/classic/browser/browser.css * skin/classic/browser/devedition.css * skin/classic/browser/browser-lightweightTheme.css skin/classic/browser/click-to-play-warning-stripes.png - skin/classic/browser/content-contextmenu.svg skin/classic/browser/Info.png skin/classic/browser/keyhole-circle.png skin/classic/browser/keyhole-circle@2x.png skin/classic/browser/subtle-pattern.png skin/classic/browser/menu-back.png skin/classic/browser/menu-forward.png skin/classic/browser/menuPanel.png skin/classic/browser/menuPanel@2x.png
copy from browser/themes/windows/content-contextmenu.svg copy to browser/themes/shared/content-contextmenu.svg --- a/browser/themes/windows/content-contextmenu.svg +++ b/browser/themes/shared/content-contextmenu.svg @@ -1,46 +1,18 @@ <?xml version="1.0"?> <!-- 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/. --> -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> <style> - use:not(:target) { + path:not(:target), + polygon:not(:target) { display: none; } - use { - fill: menutext; - } - use[id$="-active"] { - fill: -moz-menuhovertext; - } - use[id$="-disabled"] { - fill: graytext; - } </style> - <defs> - <path id="back-shape" fill-rule="evenodd" d="M1.192,8.893L2.21,9.964c0.064,0.065,0.136,0.117,0.214,0.159 l5.199,5.301c0.607,0.63,1.465,0.764,1.915,0.297l1.02-1.082c0.449-0.467,0.32-1.357-0.288-1.99l-2.116-2.158h5.705 c0.671,0,1.215-0.544,1.215-1.215v-2.43c0-0.671-0.544-1.215-1.215-1.215H8.094l2.271-2.309c0.609-0.626,0.737-1.512,0.288-1.974 L9.635,0.278C9.184-0.188,8.327-0.055,7.718,0.575L2.479,5.901C2.38,5.946,2.289,6.008,2.21,6.089L1.192,7.171 c-0.21,0.219-0.293,0.53-0.26,0.864C0.899,8.367,0.981,8.676,1.192,8.893z"/> - <path id="forward-shape" fill-rule="evenodd" d="M14.808,7.107L13.79,6.036c-0.064-0.065-0.136-0.117-0.214-0.159 L8.377,0.576C7.77-0.054,6.912-0.189,6.461,0.278L5.441,1.36c-0.449,0.467-0.32,1.357,0.288,1.99l2.116,2.158H2.14 c-0.671,0-1.215,0.544-1.215,1.215v2.43c0,0.671,0.544,1.215,1.215,1.215h5.765l-2.271,2.309c-0.609,0.626-0.737,1.512-0.288,1.974 l1.019,1.072c0.451,0.465,1.308,0.332,1.917-0.297l5.238-5.326c0.1-0.045,0.191-0.107,0.269-0.188l1.019-1.082 c0.21-0.219,0.293-0.53,0.26-0.864C15.101,7.633,15.019,7.324,14.808,7.107z"/> - <path id="reload-shape" fill-rule="evenodd" d="M15.429,8h-8l3.207-3.207C9.889,4.265,8.986,3.947,8,3.947 c-2.554,0-4.625,2.071-4.625,4.625S5.446,13.196,8,13.196c1.638,0,3.069-0.857,3.891-2.141l2.576,1.104 C13.199,14.439,10.794,16,8,16c-4.103,0-7.429-3.326-7.429-7.429S3.897,1.143,8,1.143c1.762,0,3.366,0.624,4.631,1.654L15.429,0V8z"/> - <polygon id="stop-shape" points="16,2.748 13.338,0.079 8.038,5.391 2.661,0 0,2.669 5.377,8.059 0.157,13.292 2.819,15.961 8.039,10.728 13.298,16 15.959,13.331 10.701,8.06"/> - <path id="bookmark-shape" d="M8.008,3.632l0.986,2.012l0.452,0.922l1.014,0.169l2.326,0.389l-1.719,1.799l-0.676,0.708l0.145,0.967 L10.896,13l-1.959-1.039l-0.937-0.497l-0.937,0.497l-1.957,1.038L5.468,10.6l0.146-0.968L4.937,8.924L3.219,7.126l2.351-0.39 l1.023-0.17l0.45-0.934L8.008,3.632 M8,0C7.72,0,7.44,0.217,7.228,0.65L5.242,4.766L0.907,5.485c-0.958,0.159-1.195,0.861-0.53,1.56 l3.113,3.258l-0.69,4.583c-0.105,0.689,0.172,1.092,0.658,1.092c0.185,0,0.399-0.058,0.635-0.181l3.906-2.072l3.906,2.072 c0.236,0.123,0.45,0.181,0.635,0.181c0.486,0,0.762-0.403,0.659-1.092l-0.687-4.583l3.109-3.255c0.666-0.702,0.428-1.404-0.53-1.564 l-4.303-0.719L8.772,0.65C8.56,0.217,8.28,0,8,0L8,0z"/> - <path id="bookmarked-shape" d="M8,0C7.719,0,7.438,0.217,7.225,0.651L5.233,4.773l-4.35,0.72c-0.961,0.159-1.199,0.862-0.531,1.562 l3.124,3.262l-0.692,4.589C2.679,15.596,2.957,16,3.444,16c0.185,0,0.401-0.058,0.637-0.181L8,13.744l3.919,2.075 C12.156,15.942,12.372,16,12.557,16c0.487,0,0.764-0.404,0.661-1.094l-0.69-4.589l3.12-3.259c0.668-0.703,0.43-1.406-0.532-1.566 l-4.317-0.72L8.775,0.651C8.562,0.217,8.281,0,8,0L8,0z"/> - </defs> - <use id="back" xlink:href="#back-shape"/> - <use id="back-active" xlink:href="#back-shape"/> - <use id="back-disabled" xlink:href="#back-shape"/> - <use id="forward" xlink:href="#forward-shape"/> - <use id="forward-active" xlink:href="#forward-shape"/> - <use id="forward-disabled" xlink:href="#forward-shape"/> - <use id="reload" xlink:href="#reload-shape"/> - <use id="reload-active" xlink:href="#reload-shape"/> - <use id="reload-disabled" xlink:href="#reload-shape"/> - <use id="stop" xlink:href="#stop-shape"/> - <use id="stop-active" xlink:href="#stop-shape"/> - <use id="stop-disabled" xlink:href="#stop-shape"/> - <use id="bookmark" xlink:href="#bookmark-shape"/> - <use id="bookmark-active" xlink:href="#bookmark-shape"/> - <use id="bookmark-disabled" xlink:href="#bookmark-shape"/> - <use id="bookmarked" xlink:href="#bookmarked-shape"/> - <use id="bookmarked-active" xlink:href="#bookmarked-shape"/> - <use id="bookmarked-disabled" xlink:href="#bookmarked-shape"/> + <path id="back" fill-rule="evenodd" d="M1.192,8.893L2.21,9.964c0.064,0.065,0.136,0.117,0.214,0.159 l5.199,5.301c0.607,0.63,1.465,0.764,1.915,0.297l1.02-1.082c0.449-0.467,0.32-1.357-0.288-1.99l-2.116-2.158h5.705 c0.671,0,1.215-0.544,1.215-1.215v-2.43c0-0.671-0.544-1.215-1.215-1.215H8.094l2.271-2.309c0.609-0.626,0.737-1.512,0.288-1.974 L9.635,0.278C9.184-0.188,8.327-0.055,7.718,0.575L2.479,5.901C2.38,5.946,2.289,6.008,2.21,6.089L1.192,7.171 c-0.21,0.219-0.293,0.53-0.26,0.864C0.899,8.367,0.981,8.676,1.192,8.893z"/> + <path id="forward" fill-rule="evenodd" d="M14.808,7.107L13.79,6.036c-0.064-0.065-0.136-0.117-0.214-0.159 L8.377,0.576C7.77-0.054,6.912-0.189,6.461,0.278L5.441,1.36c-0.449,0.467-0.32,1.357,0.288,1.99l2.116,2.158H2.14 c-0.671,0-1.215,0.544-1.215,1.215v2.43c0,0.671,0.544,1.215,1.215,1.215h5.765l-2.271,2.309c-0.609,0.626-0.737,1.512-0.288,1.974 l1.019,1.072c0.451,0.465,1.308,0.332,1.917-0.297l5.238-5.326c0.1-0.045,0.191-0.107,0.269-0.188l1.019-1.082 c0.21-0.219,0.293-0.53,0.26-0.864C15.101,7.633,15.019,7.324,14.808,7.107z"/> + <path id="reload" fill-rule="evenodd" d="M15.429,8h-8l3.207-3.207C9.889,4.265,8.986,3.947,8,3.947 c-2.554,0-4.625,2.071-4.625,4.625S5.446,13.196,8,13.196c1.638,0,3.069-0.857,3.891-2.141l2.576,1.104 C13.199,14.439,10.794,16,8,16c-4.103,0-7.429-3.326-7.429-7.429S3.897,1.143,8,1.143c1.762,0,3.366,0.624,4.631,1.654L15.429,0V8z"/> + <polygon id="stop" points="16,2.748 13.338,0.079 8.038,5.391 2.661,0 0,2.669 5.377,8.059 0.157,13.292 2.819,15.961 8.039,10.728 13.298,16 15.959,13.331 10.701,8.06"/> + <path id="bookmark" d="M8.008,3.632l0.986,2.012l0.452,0.922l1.014,0.169l2.326,0.389l-1.719,1.799l-0.676,0.708l0.145,0.967 L10.896,13l-1.959-1.039l-0.937-0.497l-0.937,0.497l-1.957,1.038L5.468,10.6l0.146-0.968L4.937,8.924L3.219,7.126l2.351-0.39 l1.023-0.17l0.45-0.934L8.008,3.632 M8,0C7.72,0,7.44,0.217,7.228,0.65L5.242,4.766L0.907,5.485c-0.958,0.159-1.195,0.861-0.53,1.56 l3.113,3.258l-0.69,4.583c-0.105,0.689,0.172,1.092,0.658,1.092c0.185,0,0.399-0.058,0.635-0.181l3.906-2.072l3.906,2.072 c0.236,0.123,0.45,0.181,0.635,0.181c0.486,0,0.762-0.403,0.659-1.092l-0.687-4.583l3.109-3.255c0.666-0.702,0.428-1.404-0.53-1.564 l-4.303-0.719L8.772,0.65C8.56,0.217,8.28,0,8,0L8,0z"/> + <path id="bookmarked" d="M8,0C7.719,0,7.438,0.217,7.225,0.651L5.233,4.773l-4.35,0.72c-0.961,0.159-1.199,0.862-0.531,1.562 l3.124,3.262l-0.692,4.589C2.679,15.596,2.957,16,3.444,16c0.185,0,0.401-0.058,0.637-0.181L8,13.744l3.919,2.075 C12.156,15.942,12.372,16,12.557,16c0.487,0,0.764-0.404,0.661-1.094l-0.69-4.589l3.12-3.259c0.668-0.703,0.43-1.406-0.532-1.566 l-4.317-0.72L8.775,0.651C8.562,0.217,8.281,0,8,0L8,0z"/> </svg>
--- a/browser/themes/shared/contextmenu.inc.css +++ b/browser/themes/shared/contextmenu.inc.css @@ -3,95 +3,49 @@ -moz-box-pack: center; -moz-box-align: center; } #context-navigation > .menuitem-iconic > .menu-iconic-left { -moz-appearance: none; } +#context-navigation > .menuitem-iconic > .menu-iconic-left > .menu-iconic-icon { + width: 16px; + height: 16px; + margin: 7px; + filter: url(chrome://browser/skin/filters.svg#fill); + fill: currentColor; +} + #context-back { list-style-image: url("chrome://browser/skin/content-contextmenu.svg#back"); } -#context-back[_moz-menuactive=true] { - list-style-image: url("chrome://browser/skin/content-contextmenu.svg#back-active"); -} - -#context-back[disabled=true] { - list-style-image: url("chrome://browser/skin/content-contextmenu.svg#back-disabled"); -} - #context-forward { list-style-image: url("chrome://browser/skin/content-contextmenu.svg#forward"); } -#context-forward[_moz-menuactive=true] { - list-style-image: url("chrome://browser/skin/content-contextmenu.svg#forward-active"); -} - -#context-forward[disabled=true] { - list-style-image: url("chrome://browser/skin/content-contextmenu.svg#forward-disabled"); -} - #context-reload { list-style-image: url("chrome://browser/skin/content-contextmenu.svg#reload"); } -#context-reload[_moz-menuactive=true] { - list-style-image: url("chrome://browser/skin/content-contextmenu.svg#reload-active"); -} - -#context-reload[disabled=true] { - list-style-image: url("chrome://browser/skin/content-contextmenu.svg#reload-disabled"); -} - #context-stop { list-style-image: url("chrome://browser/skin/content-contextmenu.svg#stop"); } -#context-stop[_moz-menuactive=true] { - list-style-image: url("chrome://browser/skin/content-contextmenu.svg#stop-active"); -} - -#context-stop[disabled=true] { - list-style-image: url("chrome://browser/skin/content-contextmenu.svg#stop-disabled"); -} - #context-bookmarkpage { list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmark"); } -#context-bookmarkpage[_moz-menuactive=true] { - list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmark-active"); -} - -#context-bookmarkpage[disabled=true] { - list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmark-disabled"); -} - #context-bookmarkpage[starred=true] { list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmarked"); } -#context-bookmarkpage[starred=true][_moz-menuactive=true] { - list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmarked-active"); -} - -#context-bookmarkpage[starred=true][disabled=true] { - list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmarked-disabled"); -} - #context-back:-moz-locale-dir(rtl), #context-forward:-moz-locale-dir(rtl), #context-reload:-moz-locale-dir(rtl) { transform: scaleX(-1); } -#context-navigation > .menuitem-iconic > .menu-iconic-left > .menu-iconic-icon { - width: 16px; - height: 16px; - margin: 7px; -} - #context-media-eme-learnmore { list-style-image: url("chrome://browser/skin/drm-icon.svg#chains"); }
--- a/browser/themes/shared/downloads/downloads.inc.css +++ b/browser/themes/shared/downloads/downloads.inc.css @@ -47,17 +47,17 @@ min-width: 0; border-left: 1px solid hsla(210,4%,10%,.14); -moz-appearance: none !important; } .downloadsPanelFooterButton { -moz-appearance: none; background-color: transparent; - color: black; + color: inherit; margin: 0; padding: 0; min-height: 40px; } .downloadsPanelFooterButton:hover { outline: 1px solid hsla(210,4%,10%,.07); background-color: hsla(210,4%,10%,.07);
--- a/browser/themes/shared/jar.inc.mn +++ b/browser/themes/shared/jar.inc.mn @@ -10,16 +10,17 @@ skin/classic/browser/aboutNetError.css (../shared/aboutNetError.css) skin/classic/browser/blockedSite.css (../shared/blockedSite.css) skin/classic/browser/error-pages.css (../shared/error-pages.css) * skin/classic/browser/aboutProviderDirectory.css (../shared/aboutProviderDirectory.css) * skin/classic/browser/aboutSessionRestore.css (../shared/aboutSessionRestore.css) skin/classic/browser/aboutSocialError.css (../shared/aboutSocialError.css) skin/classic/browser/aboutTabCrashed.css (../shared/aboutTabCrashed.css) skin/classic/browser/aboutWelcomeBack.css (../shared/aboutWelcomeBack.css) + skin/classic/browser/content-contextmenu.svg (../shared/content-contextmenu.svg) skin/classic/browser/addons/addon-install-blocked.svg (../shared/addons/addon-install-blocked.svg) skin/classic/browser/addons/addon-install-confirm.svg (../shared/addons/addon-install-confirm.svg) skin/classic/browser/addons/addon-install-downloading.svg (../shared/addons/addon-install-downloading.svg) skin/classic/browser/addons/addon-install-error.svg (../shared/addons/addon-install-error.svg) skin/classic/browser/addons/addon-install-installed.svg (../shared/addons/addon-install-installed.svg) skin/classic/browser/addons/addon-install-restart.svg (../shared/addons/addon-install-restart.svg) skin/classic/browser/addons/addon-install-warning.svg (../shared/addons/addon-install-warning.svg) skin/classic/browser/addons/addon-install-anchor.svg (../shared/addons/addon-install-anchor.svg)
--- a/browser/themes/windows/browser-aero.css +++ b/browser/themes/windows/browser-aero.css @@ -327,23 +327,16 @@ } } #main-window[darkwindowframe="true"] #toolbar-menubar:not(:-moz-lwtheme):not(:-moz-window-inactive), #main-window[darkwindowframe="true"] #TabsToolbar:not(:-moz-lwtheme):not(:-moz-window-inactive) { color: white; } - @media (-moz-os-version: windows-vista), - (-moz-os-version: windows-win7),{ - #toolbar-menubar:not(:-moz-lwtheme) { - text-shadow: 0 0 .5em white, 0 0 .5em white, 0 1px 0 rgba(255,255,255,.4); - } - } - /* Show borders on vista through win8, but not on win10 and later: */ @media (-moz-os-version: windows-vista), (-moz-os-version: windows-win7), (-moz-os-version: windows-win8) { /* Vertical toolbar border */ #main-window:not([customizing])[sizemode=normal] #navigator-toolbox:not(:-moz-lwtheme)::after, #main-window:not([customizing])[sizemode=normal] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme), #main-window:not([customizing])[sizemode=normal] #navigator-toolbox:-moz-lwtheme, @@ -371,52 +364,50 @@ #main-window[sizemode=normal] #TabsToolbar { padding-left: 1px; padding-right: 1px; } #appcontent:not(:-moz-lwtheme) { background-color: -moz-dialog; } - - #main-menubar:not(:-moz-lwtheme):not(:-moz-window-inactive) { - background-color: rgba(255,255,255,.5); - color: black; - } - - @media (-moz-os-version: windows-vista), - (-moz-os-version: windows-win7) { - #main-menubar:not(:-moz-lwtheme):not(:-moz-window-inactive) { - border-radius: 4px; - } - - /* Artificially draw window borders that are covered by lwtheme, see bug 591930. - * We use a different border for win8, and this is not necessary on win10+ */ - #main-window[sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme { - border-top: 2px solid; - -moz-border-top-colors: @glassActiveBorderColor@ rgba(255,255,255,.6); - } - - #main-window[sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme:-moz-window-inactive { - -moz-border-top-colors: @glassInactiveBorderColor@ rgba(255,255,255,.6); - } - } } @media (-moz-windows-glass) { #main-window[sizemode=normal] #nav-bar { border-top-left-radius: 2.5px; border-top-right-radius: 2.5px; } #main-window[sizemode=fullscreen]:not(:-moz-lwtheme) { -moz-appearance: none; background-color: #556; } + #toolbar-menubar:not(:-moz-lwtheme) { + text-shadow: 0 0 .5em white, 0 0 .5em white, 0 1px 0 rgba(255,255,255,.4); + } + + #main-menubar:not(:-moz-lwtheme):not(:-moz-window-inactive) { + background-color: rgba(255,255,255,.5); + color: black; + border-radius: 4px; + } + + /* Artificially draw window borders that are covered by lwtheme, see bug 591930. + * We use a different border for win8, and this is not necessary on win10+ */ + #main-window[sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme { + border-top: 2px solid; + -moz-border-top-colors: @glassActiveBorderColor@ rgba(255,255,255,.6); + } + + #main-window[sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme:-moz-window-inactive { + -moz-border-top-colors: @glassInactiveBorderColor@ rgba(255,255,255,.6); + } + /* Glass Fog */ #TabsToolbar:not(:-moz-lwtheme) { position: relative; } #TabsToolbar:not(:-moz-lwtheme)::after { /* Because we use placeholders for window controls etc. in the tabstrip,
--- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -2348,22 +2348,22 @@ notification[value="translation"] { list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png"); -moz-image-region: auto; } /* Ctrl-Tab */ #ctrlTab-panel { -moz-appearance: none; - background: rgba(27%,27%,27%,.7); + background: hsla(0,0%,33%,.85); color: white; border-style: none; padding: 20px 10px 10px; font-weight: bold; - text-shadow: 0 0 1px rgb(27%,27%,27%), 0 0 2px rgb(27%,27%,27%); + text-shadow: 0 0 1px hsl(0,0%,12%), 0 0 2px hsl(0,0%,12%); } .ctrlTab-favicon[src] { background-color: white; width: 20px; height: 20px; padding: 2px; }
--- a/browser/themes/windows/customizableui/panelUI.css +++ b/browser/themes/windows/customizableui/panelUI.css @@ -1,16 +1,16 @@ /* 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 ../../shared/customizableui/panelUI.inc.css .panel-subviews { - background-color: -moz-field; + background-color: var(--panel-arrowcontent-background); } #PanelUI-contents #zoom-out-btn { padding-left: 12px; padding-right: 12px; } #PanelUI-contents #zoom-in-btn {
--- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -12,17 +12,16 @@ browser.jar: skin/classic/browser/actionicon-tab.png skin/classic/browser/actionicon-tab@2x.png skin/classic/browser/actionicon-tab-XPVista7.png * skin/classic/browser/browser.css * skin/classic/browser/devedition.css * skin/classic/browser/browser-lightweightTheme.css skin/classic/browser/caption-buttons.svg skin/classic/browser/click-to-play-warning-stripes.png - skin/classic/browser/content-contextmenu.svg skin/classic/browser/Info.png skin/classic/browser/Info-XP.png skin/classic/browser/keyhole-forward-mask.svg skin/classic/browser/livemark-folder.png skin/classic/browser/livemark-folder-XP.png skin/classic/browser/menu-back.png skin/classic/browser/menu-back-XP.png skin/classic/browser/menu-forward.png
--- a/devtools/client/inspector/breadcrumbs.js +++ b/devtools/client/inspector/breadcrumbs.js @@ -6,17 +6,16 @@ "use strict"; /* eslint-disable mozilla/reject-some-requires */ const {Ci} = require("chrome"); /* eslint-enable mozilla/reject-some-requires */ const Services = require("Services"); const promise = require("promise"); -const FocusManager = Services.focus; const ELLIPSIS = Services.prefs.getComplexValue( "intl.ellipsis", Ci.nsIPrefLocalizedString).data; const MAX_LABEL_LENGTH = 40; const NS_XHTML = "http://www.w3.org/1999/xhtml"; const SCROLL_REPEAT_MS = 100; @@ -351,25 +350,26 @@ HTMLBreadcrumbs.prototype = { this.outer.addEventListener("mouseout", this, true); this.outer.addEventListener("focus", this, true); this.shortcuts = new KeyShortcuts({ window: this.chromeWin, target: this.outer }); this.handleShortcut = this.handleShortcut.bind(this); this.shortcuts.on("Right", this.handleShortcut); this.shortcuts.on("Left", this.handleShortcut); - this.shortcuts.on("Tab", this.handleShortcut); - this.shortcuts.on("Shift+Tab", this.handleShortcut); // We will save a list of already displayed nodes in this array. this.nodeHierarchy = []; // Last selected node in nodeHierarchy. this.currentIndex = -1; + // Used to build a unique breadcrumb button Id. + this.breadcrumbsWidgetItemId = 0; + this.update = this.update.bind(this); this.updateSelectors = this.updateSelectors.bind(this); this.selection.on("new-node-front", this.update); this.selection.on("pseudoclass", this.updateSelectors); this.selection.on("attribute-changed", this.updateSelectors); this.inspector.on("markupmutation", this.update); this.update(); }, @@ -482,41 +482,35 @@ HTMLBreadcrumbs.prototype = { } else if (event.type == "mouseout") { this.handleMouseOut(event); } else if (event.type == "focus") { this.handleFocus(event); } }, /** - * Focus event handler. When breadcrumbs container gets focus, if there is an - * already selected breadcrumb, move focus to it. + * Focus event handler. When breadcrumbs container gets focus, + * aria-activedescendant needs to be updated to currently selected + * breadcrumb. Ensures that the focus stays on the container at all times. * @param {DOMEvent} event. */ handleFocus: function (event) { - let control = this.container.querySelector( - ".breadcrumbs-widget-item[checked]"); - if (!this.suspendFocus && control && control !== event.target) { - // If we already have a selected breadcrumb and focus target is not it, - // move focus to selected breadcrumb - event.preventDefault(); - control.focus(); - } - this.suspendFocus = false; + event.stopPropagation(); + + this.outer.setAttribute("aria-activedescendant", + this.nodeHierarchy[this.currentIndex].button.id); + + this.outer.focus(); }, /** * On click navigate to the correct node. * @param {DOMEvent} event. */ handleClick: function (event) { - // When clicking a button temporarily suspend the behaviour that refocuses - // the currently selected button, to prevent flicking back to that button - // See Bug 1272011 - this.suspendFocus = true; let target = event.originalTarget; if (target.tagName == "button") { target.onBreadcrumbsClick(); } }, /** * On mouse over, highlight the corresponding content DOM Node. @@ -549,38 +543,27 @@ HTMLBreadcrumbs.prototype = { if (!this.selection.isElementNode()) { return; } event.preventDefault(); event.stopPropagation(); this.keyPromise = (this.keyPromise || promise.resolve(null)).then(() => { + let currentnode; if (name === "Left" && this.currentIndex != 0) { - let node = this.nodeHierarchy[this.currentIndex - 1].node; - return this.selection.setNodeFront(node, "breadcrumbs"); + currentnode = this.nodeHierarchy[this.currentIndex - 1]; } else if (name === "Right" && this.currentIndex < this.nodeHierarchy.length - 1) { - let node = this.nodeHierarchy[this.currentIndex + 1].node; - return this.selection.setNodeFront(node, "breadcrumbs"); - } else if (name === "Tab") { - // To move focus to next element following the breadcrumbs, relative - // element needs to be the last element in breadcrumbs' subtree. - let last = this.container.lastChild; - while (last && last.lastChild) { - last = last.lastChild; - } - FocusManager.moveFocus(this.chromeWin, last, FocusManager.MOVEFOCUS_FORWARD, 0); - } else if (name === "Shift+Tab") { - // Tabbing when breadcrumbs or its contents are focused should move focus to - // previous focusable element relative to breadcrumbs themselves. - let elt = this.container; - FocusManager.moveFocus(this.chromeWin, elt, FocusManager.MOVEFOCUS_BACKWARD, 0); + currentnode = this.nodeHierarchy[this.currentIndex + 1]; + } else { + return null; } - return null; + this.outer.setAttribute("aria-activedescendant", currentnode.button.id); + return this.selection.setNodeFront(currentnode.node, "breadcrumbs"); }); }, /** * Remove nodes and clean up. */ destroy: function () { this.selection.off("new-node-front", this.update); @@ -666,17 +649,19 @@ HTMLBreadcrumbs.prototype = { * Build a button representing the node. * @param {NodeFront} node The node from the page. * @return {DOMNode} The <button> for this node. */ buildButton: function (node) { let button = this.chromeDoc.createElementNS(NS_XHTML, "button"); button.appendChild(this.prettyPrintNodeAsXHTML(node)); button.className = "breadcrumbs-widget-item"; + button.id = "breadcrumbs-widget-item-" + this.breadcrumbsWidgetItemId++; + button.setAttribute("tabindex", "-1"); button.setAttribute("title", this.prettyPrintNodeAsText(node)); button.onclick = () => { button.focus(); }; button.onBreadcrumbsClick = () => { this.selection.setNodeFront(node, "breadcrumbs");
--- a/devtools/client/inspector/inspector.xul +++ b/devtools/client/inspector/inspector.xul @@ -49,17 +49,18 @@ title="&inspectorEyeDropper.label;" class="devtools-button command-button-invertable" /> <div xmlns="http://www.w3.org/1999/xhtml" id="inspector-sidebar-toggle-box" /> </html:div> <vbox flex="1" id="markup-box"> </vbox> <html:div id="inspector-breadcrumbs-toolbar" class="devtools-toolbar"> - <html:div id="inspector-breadcrumbs" class="breadcrumbs-widget-container"/> + <html:div id="inspector-breadcrumbs" class="breadcrumbs-widget-container" + role="group" aria-label="&inspectorBreadcrumbsGroup;" tabindex="0" /> </html:div> </vbox> <splitter class="devtools-side-splitter"/> <vbox id="inspector-sidebar-container"> <!-- Specify the XHTML namespace explicitly otherwise the layout is broken. --> <div xmlns="http://www.w3.org/1999/xhtml" id="inspector-sidebar"
--- a/devtools/client/inspector/test/browser_inspector_breadcrumbs_keybinding.js +++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_keybinding.js @@ -57,12 +57,15 @@ add_task(function* () { } EventUtils.synthesizeKey(key, {}); yield onUpdated; let newNodeFront = yield getNodeFront(newSelection, inspector); is(newNodeFront, inspector.selection.nodeFront, "The current selection is correct"); + is(container.getAttribute("aria-activedescendant"), + container.querySelector("button[checked]").id, + "aria-activedescendant is set correctly"); currentSelection = newSelection; } });
--- a/devtools/client/inspector/test/browser_inspector_breadcrumbs_keyboard_trap.js +++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_keyboard_trap.js @@ -56,24 +56,28 @@ add_task(function* () { let container = doc.getElementById("inspector-breadcrumbs"); let button = container.querySelector("button[checked]"); let onHighlight = toolbox.once("node-highlight"); button.click(); yield onHighlight; // Ensure a breadcrumb is focused. - is(doc.activeElement, button, "Focus is on selected breadcrumb"); + is(doc.activeElement, container, "Focus is on selected breadcrumb"); + is(container.getAttribute("aria-activedescendant"), button.id, + "aria-activedescendant is set correctly"); for (let { desc, focused, key, options } of TEST_DATA) { info(desc); EventUtils.synthesizeKey(key, options); // Wait until the keyPromise promise resolves. yield breadcrumbs.keyPromise; if (focused) { - is(doc.activeElement, button, "Focus is on selected breadcrumb"); + is(doc.activeElement, container, "Focus is on selected breadcrumb"); } else { ok(!containsFocus(doc, container), "Focus is outside of breadcrumbs"); } + is(container.getAttribute("aria-activedescendant"), button.id, + "aria-activedescendant is set correctly"); } });
--- a/devtools/client/locales/en-US/inspector.dtd +++ b/devtools/client/locales/en-US/inspector.dtd @@ -14,9 +14,14 @@ the inspector toolbar for the button that lets users add elements to the DOM (as children of the currently selected element). --> <!ENTITY inspectorAddNode.label "Create New Node"> <!ENTITY inspectorAddNode.accesskey "C"> <!-- LOCALIZATION NOTE (inspectorEyeDropper.label): A string displayed as the tooltip of a button in the inspector which toggles the Eyedropper tool --> -<!ENTITY inspectorEyeDropper.label "Grab a color from the page"> \ No newline at end of file +<!ENTITY inspectorEyeDropper.label "Grab a color from the page"> + +<!-- LOCALIZATION NOTE (inspectorBreadcrumbsGroup): A string visible only to a + screen reader and is used to label (using aria-label attribute) a container + for inspector breadcrumbs --> +<!ENTITY inspectorBreadcrumbsGroup "Breadcrumbs">
--- a/devtools/client/themes/widgets.css +++ b/devtools/client/themes/widgets.css @@ -244,25 +244,16 @@ min-width: 65px; margin: 0; padding: 0 8px 0 20px; border: none; outline: none; color: hsl(210,30%,85%); } -.breadcrumbs-widget-item:-moz-focusring { - outline: none; -} - -.breadcrumbs-widget-item[checked]:-moz-focusring > .button-box { - outline: var(--theme-focus-outline); - outline-offset: -1px; -} - .breadcrumbs-widget-item > .button-box { border: none; padding-top: 0; padding-bottom: 0; } :root[platform="win"] .breadcrumbs-widget-item:-moz-focusring > .button-box { border-width: 0;
--- a/dom/animation/KeyframeUtils.cpp +++ b/dom/animation/KeyframeUtils.cpp @@ -35,17 +35,17 @@ namespace mozilla { const double kNotPaceable = -1.0; // For the aAllowList parameter of AppendStringOrStringSequence and // GetPropertyValuesPairs. enum class ListAllowance { eDisallow, eAllow }; /** * A comparator to sort nsCSSProperty values such that longhands are sorted - * before shorthands, and shorthands with less components are sorted before + * before shorthands, and shorthands with fewer components are sorted before * shorthands with more components. * * Using this allows us to prioritize values specified by longhands (or smaller * shorthand subsets) when longhands and shorthands are both specified * on the one keyframe. * * Example orderings that result from this: *
--- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -286,16 +286,65 @@ Element::UpdateEditableState(bool aNotif AddStatesSilently(NS_EVENT_STATE_MOZ_READWRITE); } else { RemoveStatesSilently(NS_EVENT_STATE_MOZ_READWRITE); AddStatesSilently(NS_EVENT_STATE_MOZ_READONLY); } } } +int32_t +Element::TabIndex() +{ + const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(nsGkAtoms::tabindex); + if (attrVal && attrVal->Type() == nsAttrValue::eInteger) { + return attrVal->GetIntegerValue(); + } + + return TabIndexDefault(); +} + +void +Element::Focus(mozilla::ErrorResult& aError) +{ + nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(this); + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm && domElement) { + aError = fm->SetFocus(domElement, 0); + } +} + +void +Element::SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError) +{ + nsAutoString value; + value.AppendInt(aTabIndex); + + SetAttr(nsGkAtoms::tabindex, value, aError); +} + +void +Element::Blur(mozilla::ErrorResult& aError) +{ + if (!ShouldBlur(this)) { + return; + } + + nsIDocument* doc = GetComposedDoc(); + if (!doc) { + return; + } + + nsPIDOMWindowOuter* win = doc->GetWindow(); + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (win && fm) { + aError = fm->ClearFocus(win); + } +} + EventStates Element::StyleStateFromLocks() const { EventStates locks = LockedStyleStates(); EventStates state = mState | locks; if (locks.HasState(NS_EVENT_STATE_VISITED)) { return state & ~NS_EVENT_STATE_UNVISITED;
--- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -188,16 +188,41 @@ public: */ void UpdateState(bool aNotify); /** * Method to update mState with link state information. This does not notify. */ void UpdateLinkState(EventStates aState); + virtual int32_t TabIndexDefault() + { + return -1; + } + + /** + * Get tabIndex of this element. If not found, return TabIndexDefault. + */ + int32_t TabIndex(); + + /** + * Set tabIndex value to this element. + */ + void SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError); + + /** + * Make focus on this element. + */ + virtual void Focus(mozilla::ErrorResult& aError); + + /** + * Show blur and clear focus. + */ + virtual void Blur(mozilla::ErrorResult& aError); + /** * The style state of this element. This is the real state of the element * with any style locks applied for pseudo-class inspecting. */ EventStates StyleState() const { if (!HasLockedStyleStates()) { return mState;
--- a/dom/base/ScriptSettings.cpp +++ b/dom/base/ScriptSettings.cpp @@ -807,9 +807,27 @@ AutoSafeJSContext::AutoSafeJSContext(MOZ DebugOnly<bool> ok = Init(xpc::UnprivilegedJunkScope()); MOZ_ASSERT(ok, "This is quite odd. We should have crashed in the " "xpc::NativeGlobal() call if xpc::UnprivilegedJunkScope() " "returned null, and inited correctly otherwise!"); } +AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) + : AutoJSAPI() +{ + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + + Init(); +} + +void +AutoSlowOperation::CheckForInterrupt() +{ + // JS_CheckForInterrupt expects us to be in a compartment. + JSAutoCompartment ac(cx(), xpc::UnprivilegedJunkScope()); + JS_CheckForInterrupt(cx()); +} + } // namespace mozilla
--- a/dom/base/ScriptSettings.h +++ b/dom/base/ScriptSettings.h @@ -438,11 +438,28 @@ public: { return cx(); } private: MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; +/** + * Use AutoSlowOperation when native side calls many JS callbacks in a row + * and slow script dialog should be activated if too much time is spent going + * through those callbacks. + * AutoSlowOperation puts a JSAutoRequest on the stack so that we don't continue + * to reset the watchdog and CheckForInterrupt can be then used to check whether + * JS execution should be interrupted. + */ +class MOZ_RAII AutoSlowOperation : public dom::AutoJSAPI +{ +public: + explicit AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); + void CheckForInterrupt(); +private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + } // namespace mozilla #endif // mozilla_dom_ScriptSettings_h
--- a/dom/base/nsDOMMutationObserver.cpp +++ b/dom/base/nsDOMMutationObserver.cpp @@ -880,20 +880,17 @@ nsDOMMutationObserver::HandleMutationsIn // after previous mutations are handled. But in case some // callback calls a sync API, which spins the eventloop, we need to still // process other mutations happening during that sync call. // This does *not* catch all cases, but should work for stuff running // in separate tabs. return; } - // We need the AutoSafeJSContext to ensure the slow script dialog is - // triggered. AutoSafeJSContext does that by pushing JSAutoRequest to stack. - // This needs to be outside the while loop. - AutoSafeJSContext cx; + AutoSlowOperation aso; nsTArray<RefPtr<nsDOMMutationObserver> >* suppressedObservers = nullptr; while (sScheduledMutationObservers) { AutoTArray<RefPtr<nsDOMMutationObserver>, 4>* observers = sScheduledMutationObservers; sScheduledMutationObservers = nullptr; for (uint32_t i = 0; i < observers->Length(); ++i) { @@ -905,17 +902,17 @@ nsDOMMutationObserver::HandleMutationsIn suppressedObservers = new nsTArray<RefPtr<nsDOMMutationObserver> >; } if (!suppressedObservers->Contains(sCurrentObserver)) { suppressedObservers->AppendElement(sCurrentObserver); } } } delete observers; - JS_CheckForInterrupt(cx); + aso.CheckForInterrupt(); } if (suppressedObservers) { for (uint32_t i = 0; i < suppressedObservers->Length(); ++i) { static_cast<nsDOMMutationObserver*>(suppressedObservers->ElementAt(i))-> RescheduleForRun(); } delete suppressedObservers;
--- a/dom/base/test/fileutils.js +++ b/dom/base/test/fileutils.js @@ -95,17 +95,17 @@ function getXHRLoadHandler(expectedResul is(event.target.status, 200, "[XHR] no error in test " + testName); // Do not use |is(convertXHRBinary(event.target.responseText), expectedResult, "...");| that may output raw binary data. var convertedData = convertXHRBinary(event.target.responseText); is(convertedData.length, expectedResult.length, "[XHR] Length of result in test " + testName); ok(convertedData == expectedResult, "[XHR] Content of result in test " + testName); - is(event.lengthComputable, true, + is(event.lengthComputable, event.total != 0, "[XHR] lengthComputable in test " + testName); is(event.loaded, expectedLength, "[XHR] Loaded length in test " + testName); is(event.total, expectedLength, "[XHR] Total length in test " + testName); testHasRun(); }
--- a/dom/base/test/test_bug435425.html +++ b/dom/base/test/test_bug435425.html @@ -19,39 +19,61 @@ https://bugzilla.mozilla.org/show_bug.cg /** Test for Bug 435425 **/ var xhr = null; var upload = null; var currentEvents = null; var expectedResponseText = null; var uploadTotal = 0; +var currentProgress = -1; function logEvent(evt) { var i = 0; while ((currentEvents.length != i) && currentEvents[i].optional && ((currentEvents[i].type != evt.type) || !(evt.target instanceof currentEvents[i].target))) { ++i; } + if (evt.target instanceof XMLHttpRequestUpload) { if (evt.type == "loadstart") { uploadTotal = evt.total } else { if (evt.type == "progress") { - ok(evt.lengthComputable, "event(" + evt.type + ").lengthComputable should be true."); + is(evt.lengthComputable, evt.total != 0, "event(" + evt.type + ").lengthComputable should be " + (evt.total != 0 ? true : false) + "."); } - is(evt.total, uploadTotal, "event(" + evt.type + ").total should not change during upload."); + if (evt.total != uploadTotal && evt.total != 0) { + ok(false, "event(" + evt.type + ").total should not change during upload except to become 0 on error/abort/timeout."); + } } } + + // There can be any number of repeated progress events, so special-case this. + if (evt.type == "progress") { + // Progress events can repeat, but their "loaded" value must increase. + if (currentProgress >= 0) { + ok(currentProgress < evt.loaded, "Progress should increase, got " + + evt.loaded + " after " + currentProgress); + currentProgress = evt.loaded; + return; // stay at the currentEvent, since we got progress instead of it. + } + // Starting a new progress event group. + currentProgress = evt.loaded; + } else { + // Reset the progress indicator on any other event type. + currentProgress = -1; + } + ok(i != currentEvents.length, "Extra or wrong event?"); is(evt.type, currentEvents[i].type, "Wrong event!") ok(evt.target instanceof currentEvents[i].target, "Wrong event target [" + evt.target + "," + evt.type + "]!"); + // If we handled non-optional event, remove all optional events before the // handled event and then the non-optional event from the list. if (!currentEvents[i].optional) { for (;i != -1; --i) { currentEvents.shift(); } } } @@ -79,16 +101,17 @@ function openXHR(xhr, method, url, privi xhr.open(method, url); } function start(obj) { xhr = new XMLHttpRequest(); upload = xhr.upload; currentEvents = obj.expectedEvents; expectedResponseText = obj.withUpload; + currentProgress = -1; xhr.onload = function(evt) { if (expectedResponseText) { is(evt.target.responseText, expectedResponseText, "Wrong responseText"); } logEvent(evt); } xhr.onerror = @@ -184,211 +207,219 @@ for (var largeLength = 0; largeLength < const XHR = XMLHttpRequest; const UPLOAD = XMLHttpRequestUpload; var tests = [ { method: "GET", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, - {target: XHR, type: "progress", optional: true}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "load", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: none, testAbort: true, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "abort", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: none, testAbort: false, testRedirectError: true, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: XHR, type: "error", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: true, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: XHR, type: "error", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, - {target: XHR, type: "progress", optional: true}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "load", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: small, testAbort: true, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "abort", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: small, testAbort: false, testRedirectError: true, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: XHR, type: "error", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: true, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: XHR, type: "error", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, - {target: XHR, type: "progress", optional: true}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "load", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: mid, testAbort: true, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "abort", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: mid, testAbort: false, testRedirectError: true, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: XHR, type: "error", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: true, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: XHR, type: "error", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, - {target: XHR, type: "progress", optional: true}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "load", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: large, testAbort: true, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "abort", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: large, testAbort: false, testRedirectError: true, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: XHR, type: "error", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "GET", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: true, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: XHR, type: "error", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, - {target: XHR, type: "progress", optional: true}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "load", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: none, testAbort: true, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "abort", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: none, testAbort: false, testRedirectError: true, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: XHR, type: "error", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: true, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: XHR, type: "error", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: UPLOAD, type: "loadstart", optional: false}, - {target: UPLOAD, type: "progress", optional: true}, + {target: UPLOAD, type: "progress", optional: false}, {target: UPLOAD, type: "load", optional: false}, {target: UPLOAD, type: "loadend", optional: false}, - {target: XHR, type: "progress", optional: true}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "load", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: small, testAbort: true, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: UPLOAD, type: "loadstart", optional: false}, + {target: UPLOAD, type: "progress", optional: false}, + {target: UPLOAD, type: "abort", optional: false}, + {target: UPLOAD, type: "loadend", optional: false}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "abort", optional: false}, - {target: XHR, type: "loadend", optional: false}, - {target: UPLOAD, type: "abort", optional: false}, - {target: UPLOAD, type: "loadend", optional: false}]}, + {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: small, testAbort: false, testRedirectError: true, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: UPLOAD, type: "loadstart", optional: false}, {target: UPLOAD, type: "progress", optional: true}, + {target: UPLOAD, type: "error", optional: false}, + {target: UPLOAD, type: "loadend", optional: false}, {target: XHR, type: "error", optional: false}, - {target: XHR, type: "loadend", optional: false}, - {target: UPLOAD, type: "error", optional: false}, - {target: UPLOAD, type: "loadend", optional: false}]}, + {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: true, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: UPLOAD, type: "loadstart", optional: false}, - {target: UPLOAD, type: "progress", optional: true}, + {target: UPLOAD, type: "error", optional: false}, + {target: UPLOAD, type: "loadend", optional: false}, {target: XHR, type: "error", optional: false}, - {target: XHR, type: "loadend", optional: false}, - {target: UPLOAD, type: "error", optional: false}, - {target: UPLOAD, type: "loadend", optional: false}]}, + {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: UPLOAD, type: "loadstart", optional: false}, - {target: UPLOAD, type: "progress", optional: true}, + {target: UPLOAD, type: "progress", optional: false}, {target: UPLOAD, type: "load", optional: false}, {target: UPLOAD, type: "loadend", optional: false}, - {target: XHR, type: "progress", optional: true}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "load", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: mid, testAbort: true, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: UPLOAD, type: "loadstart", optional: false}, + {target: UPLOAD, type: "progress", optional: false}, + {target: UPLOAD, type: "abort", optional: false}, + {target: UPLOAD, type: "loadend", optional: false}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "abort", optional: false}, - {target: XHR, type: "loadend", optional: false}, - {target: UPLOAD, type: "abort", optional: false}, - {target: UPLOAD, type: "loadend", optional: false}]}, + {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: mid, testAbort: false, testRedirectError: true, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: UPLOAD, type: "loadstart", optional: false}, {target: UPLOAD, type: "progress", optional: true}, + {target: UPLOAD, type: "error", optional: false}, + {target: UPLOAD, type: "loadend", optional: false}, {target: XHR, type: "error", optional: false}, - {target: XHR, type: "loadend", optional: false}, - {target: UPLOAD, type: "error", optional: false}, - {target: UPLOAD, type: "loadend", optional: false}]}, + {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: true, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: UPLOAD, type: "loadstart", optional: false}, - {target: UPLOAD, type: "progress", optional: true}, + {target: UPLOAD, type: "error", optional: false}, + {target: UPLOAD, type: "loadend", optional: false}, {target: XHR, type: "error", optional: false}, - {target: XHR, type: "loadend", optional: false}, - {target: UPLOAD, type: "error", optional: false}, - {target: UPLOAD, type: "loadend", optional: false}]}, + {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: UPLOAD, type: "loadstart", optional: false}, - {target: UPLOAD, type: "progress", optional: true}, + {target: UPLOAD, type: "progress", optional: false}, {target: UPLOAD, type: "load", optional: false}, {target: UPLOAD, type: "loadend", optional: false}, - {target: XHR, type: "progress", optional: true}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "load", optional: false}, {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: large, testAbort: true, testRedirectError: false, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: UPLOAD, type: "loadstart", optional: false}, + {target: UPLOAD, type: "progress", optional: false}, + {target: UPLOAD, type: "abort", optional: false}, + {target: UPLOAD, type: "loadend", optional: false}, + {target: XHR, type: "progress", optional: false}, {target: XHR, type: "abort", optional: false}, - {target: XHR, type: "loadend", optional: false}, - {target: UPLOAD, type: "abort", optional: false}, - {target: UPLOAD, type: "loadend", optional: false}]}, + {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: large, testAbort: false, testRedirectError: true, testNetworkError: false, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: UPLOAD, type: "loadstart", optional: false}, {target: UPLOAD, type: "progress", optional: true}, + {target: UPLOAD, type: "error", optional: false}, + {target: UPLOAD, type: "loadend", optional: false}, {target: XHR, type: "error", optional: false}, - {target: XHR, type: "loadend", optional: false}, - {target: UPLOAD, type: "error", optional: false}, - {target: UPLOAD, type: "loadend", optional: false}]}, + {target: XHR, type: "loadend", optional: false}]}, { method: "POST", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: true, expectedEvents: [{target: XHR, type: "loadstart", optional: false}, {target: UPLOAD, type: "loadstart", optional: false}, - {target: UPLOAD, type: "progress", optional: true}, + {target: UPLOAD, type: "error", optional: false}, + {target: UPLOAD, type: "loadend", optional: false}, {target: XHR, type: "error", optional: false}, - {target: XHR, type: "loadend", optional: false}, - {target: UPLOAD, type: "error", optional: false}, - {target: UPLOAD, type: "loadend", optional: false}]}, + {target: XHR, type: "loadend", optional: false}]}, ]; function runTest() { var test = tests.shift(); start(test); } function nextTest() { - if (tests.length > 1) { + if (tests.length) { setTimeout("runTest()", 0); } else { SimpleTest.finish(); } } ok("upload" in (new XMLHttpRequest()), "XMLHttpRequest.upload isn't supported!"); SimpleTest.waitForExplicitFinish();
--- a/dom/gamepad/windows/WindowsGamepad.cpp +++ b/dom/gamepad/windows/WindowsGamepad.cpp @@ -36,20 +36,16 @@ using mozilla::ArrayLength; const unsigned kUsageDpad = 0x39; // USB HID usage tables, page 1, 0x30 = X const unsigned kFirstAxis = 0x30; // USB HID usage tables const unsigned kDesktopUsagePage = 0x1; const unsigned kButtonUsagePage = 0x9; -// Arbitrary. In practice 10 buttons/6 axes is the near maximum. -const unsigned kMaxButtons = 32; -const unsigned kMaxAxes = 32; - // Multiple devices-changed notifications can be sent when a device // is connected, because USB devices consist of multiple logical devices. // Therefore, we wait a bit after receiving one before looking for // device changes. const uint32_t kDevicesChangedStableDelay = 200; // XInput is a purely polling-driven API, so we need to // poll it periodically. 50ms is arbitrarily chosen. const uint32_t kXInputPollInterval = 50; @@ -99,46 +95,65 @@ enum GamepadType { class WindowsGamepadService; // This pointer holds a windows gamepad backend service, // it will be created and destroyed by background thread and // used by gMonitorThread WindowsGamepadService* MOZ_NON_OWNING_REF gService = nullptr; nsCOMPtr<nsIThread> gMonitorThread = nullptr; static bool sIsShutdown = false; -struct Gamepad { +class Gamepad { +public: GamepadType type; // Handle to raw input device HANDLE handle; // XInput Index of the user's controller. Passed to XInputGetState. DWORD userIndex; // Last-known state of the controller. XINPUT_STATE state; // ID from the GamepadService, also used as the index into // WindowsGamepadService::mGamepads. int id; + // Information about the physical device. unsigned numAxes; unsigned numButtons; bool hasDpad; HIDP_VALUE_CAPS dpadCaps; - bool buttons[kMaxButtons]; - struct { + nsTArray<bool> buttons; + struct axisValue { HIDP_VALUE_CAPS caps; double value; - } axes[kMaxAxes]; + }; + nsTArray<axisValue> axes; // Used during rescan to find devices that were disconnected. bool present; + + Gamepad(uint32_t aNumAxes, + uint32_t aNumButtons, + bool aHasDpad, + GamepadType aType) : + numAxes(aNumAxes), + numButtons(aNumButtons), + hasDpad(aHasDpad), + type(aType), + present(true) + { + buttons.SetLength(numButtons); + axes.SetLength(numAxes); + } +private: + Gamepad() {} }; // Drop this in favor of decltype when we require a new enough SDK. typedef void (WINAPI *XInputEnable_func)(BOOL); // RAII class to wrap loading the XInput DLL class XInputLoader { public: @@ -207,17 +222,17 @@ ScaleAxis(ULONG value, LONG min, LONG ma return 2.0 * (value - min) / (max - min) - 1.0; } /* * Given a value from a d-pad (POV hat in USB HID terminology), * represent it as 4 buttons, one for each cardinal direction. */ void -UnpackDpad(LONG dpad_value, const Gamepad* gamepad, bool buttons[kMaxButtons]) +UnpackDpad(LONG dpad_value, const Gamepad* gamepad, nsTArray<bool>& buttons) { const unsigned kUp = gamepad->numButtons - 4; const unsigned kDown = gamepad->numButtons - 3; const unsigned kLeft = gamepad->numButtons - 2; const unsigned kRight = gamepad->numButtons - 1; // Different controllers have different ways of representing // "nothing is pressed", but they're all outside the range of values. @@ -444,23 +459,22 @@ WindowsGamepadService::ScanForXInputDevi } found = true; // See if this device is already present in our list. if (HaveXInputGamepad(i)) { continue; } // Not already present, add it. - Gamepad gamepad = {}; - gamepad.type = kXInputGamepad; - gamepad.present = true; + Gamepad gamepad(kStandardGamepadAxes, + kStandardGamepadButtons, + true, + kXInputGamepad); + gamepad.userIndex = i; gamepad.state = state; - gamepad.userIndex = i; - gamepad.numButtons = kStandardGamepadButtons; - gamepad.numAxes = kStandardGamepadAxes; gamepad.id = service->AddGamepad("xinput", GamepadMappingType::Standard, kStandardGamepadButtons, kStandardGamepadAxes); mGamepads.AppendElement(gamepad); } return found; @@ -616,18 +630,16 @@ WindowsGamepadService::GetRawGamepad(HAN if (GetRawInputDeviceInfo(handle, RIDI_DEVICEINFO, &rdi, &size) == kRawInputError) { return false; } // Ensure that this is a device we care about if (!SupportedUsage(rdi.hid.usUsagePage, rdi.hid.usUsage)) { return false; } - Gamepad gamepad = {}; - // Device name is a mostly-opaque string. if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, nullptr, &size) == kRawInputError) { return false; } nsTArray<wchar_t> devname(size); devname.SetLength(size); if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, devname.Elements(), &size) == kRawInputError) { @@ -684,63 +696,68 @@ WindowsGamepadService::GetRawGamepad(HAN // Enumerate buttons. USHORT count = caps.NumberInputButtonCaps; nsTArray<HIDP_BUTTON_CAPS> buttonCaps(count); buttonCaps.SetLength(count); if (mHID.mHidP_GetButtonCaps(HidP_Input, buttonCaps.Elements(), &count, parsed) != HIDP_STATUS_SUCCESS) { return false; } + uint32_t numButtons = 0; for (unsigned i = 0; i < count; i++) { // Each buttonCaps is typically a range of buttons. - gamepad.numButtons += + numButtons += buttonCaps[i].Range.UsageMax - buttonCaps[i].Range.UsageMin + 1; } - gamepad.numButtons = std::min(gamepad.numButtons, kMaxButtons); // Enumerate value caps, which represent axes and d-pads. count = caps.NumberInputValueCaps; nsTArray<HIDP_VALUE_CAPS> valueCaps(count); valueCaps.SetLength(count); if (mHID.mHidP_GetValueCaps(HidP_Input, valueCaps.Elements(), &count, parsed) != HIDP_STATUS_SUCCESS) { return false; } nsTArray<HIDP_VALUE_CAPS> axes; // Sort the axes by usagePage and usage to expose a consistent ordering. + bool hasDpad; + HIDP_VALUE_CAPS dpadCaps; + HidValueComparator comparator; for (unsigned i = 0; i < count; i++) { if (valueCaps[i].UsagePage == kDesktopUsagePage && valueCaps[i].Range.UsageMin == kUsageDpad // Don't know how to handle d-pads that return weird values. - && valueCaps[i].LogicalMax - valueCaps[i].LogicalMin == 7 - // Can't overflow buttons - && gamepad.numButtons + 4 < kMaxButtons) { + && valueCaps[i].LogicalMax - valueCaps[i].LogicalMin == 7) { // d-pad gets special handling. // Ostensibly HID devices can expose multiple d-pads, but this // doesn't happen in practice. - gamepad.hasDpad = true; - gamepad.dpadCaps = valueCaps[i]; + hasDpad = true; + dpadCaps = valueCaps[i]; // Expose d-pad as 4 additional buttons. - gamepad.numButtons += 4; + numButtons += 4; } else { axes.InsertElementSorted(valueCaps[i], comparator); } } - gamepad.numAxes = std::min<size_t>(axes.Length(), kMaxAxes); + uint32_t numAxes = axes.Length(); + + // Not already present, add it. + Gamepad gamepad(numAxes, + numButtons, + true, + kRawInputGamepad); + + gamepad.handle = handle; + for (unsigned i = 0; i < gamepad.numAxes; i++) { - if (i >= kMaxAxes) { - break; - } gamepad.axes[i].caps = axes[i]; } - gamepad.type = kRawInputGamepad; - gamepad.handle = handle; - gamepad.present = true; + gamepad.id = service->AddGamepad(gamepad_id, GamepadMappingType::_empty, gamepad.numButtons, gamepad.numAxes); mGamepads.AppendElement(gamepad); return true; } @@ -748,18 +765,18 @@ bool WindowsGamepadService::HandleRawInput(HRAWINPUT handle) { if (!mHID) { return false; } RefPtr<GamepadPlatformService> service = GamepadPlatformService::GetParentService(); - if (service) { - return true; + if (!service) { + return false; } // First, get data from the handle UINT size; GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)); nsTArray<uint8_t> data(size); data.SetLength(size); if (GetRawInputData(handle, RID_INPUT, data.Elements(), &size, @@ -793,18 +810,21 @@ WindowsGamepadService::HandleRawInput(HR usages.SetLength(gamepad->numButtons); ULONG usageLength = gamepad->numButtons; if (mHID.mHidP_GetUsages(HidP_Input, kButtonUsagePage, 0, usages.Elements(), &usageLength, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) { return false; } - bool buttons[kMaxButtons] = { false }; - usageLength = std::min<ULONG>(usageLength, kMaxButtons); + nsTArray<bool> buttons(gamepad->numButtons); + buttons.SetLength(gamepad->numButtons); + // If we don't zero out the buttons array first, sometimes it can reuse values. + memset(buttons.Elements(), 0, gamepad->numButtons * sizeof(bool)); + for (unsigned i = 0; i < usageLength; i++) { buttons[usages[i] - 1] = true; } if (gamepad->hasDpad) { // Get d-pad position as 4 buttons. ULONG value; if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->dpadCaps.UsagePage, 0, gamepad->dpadCaps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) {
--- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -1140,16 +1140,17 @@ static nsresult FireEventForAccessibilit HTMLInputElement::HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo, FromParser aFromParser) : nsGenericHTMLFormElementWithState(aNodeInfo) , mType(kInputDefaultType->value) , mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown) , mDisabledChanged(false) , mValueChanged(false) + , mLastValueChangeWasInteractive(false) , mCheckedChanged(false) , mChecked(false) , mHandlingSelectEvent(false) , mShouldInitChecked(false) , mParserCreating(aFromParser != NOT_FROM_PARSER) , mInInternalActivate(false) , mCheckedIsToggled(false) , mIndeterminate(false) @@ -1321,16 +1322,17 @@ HTMLInputElement::Clone(mozilla::dom::No break; case VALUE_MODE_DEFAULT: if (mType == NS_FORM_INPUT_IMAGE && it->OwnerDoc()->IsStaticDocument()) { CreateStaticImageClone(it); } break; } + it->mLastValueChangeWasInteractive = mLastValueChangeWasInteractive; it.forget(aResult); return NS_OK; } nsresult HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName, nsAttrValueOrString* aValue, bool aNotify) @@ -3066,17 +3068,18 @@ HTMLInputElement::SetValueInternal(const } } else if (mType == NS_FORM_INPUT_RANGE) { nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame()); if (frame) { frame->UpdateForValueChange(); } } if (!mParserCreating) { - OnValueChanged(true); + OnValueChanged(/* aNotify = */ true, + /* aWasInteractiveUserChange = */ false); } // else DoneCreatingElement calls us again once mParserCreating is false } if (mType == NS_FORM_INPUT_COLOR) { // Update color frame, to reflect color changes nsColorControlFrame* colorControlFrame = do_QueryFrame(GetPrimaryFrame()); if (colorControlFrame) { @@ -6060,16 +6063,17 @@ HTMLInputElement::SetDirectionIfAuto(boo } NS_IMETHODIMP HTMLInputElement::Reset() { // We should be able to reset all dirty flags regardless of the type. SetCheckedChanged(false); SetValueChanged(false); + mLastValueChangeWasInteractive = false; switch (GetValueMode()) { case VALUE_MODE_VALUE: return SetDefaultValueAsValue(); case VALUE_MODE_DEFAULT_ON: DoSetChecked(DefaultChecked(), true, false); return NS_OK; case VALUE_MODE_FILENAME: @@ -6912,19 +6916,20 @@ HTMLInputElement::SetCustomValidity(cons UpdateState(true); return NS_OK; } bool HTMLInputElement::IsTooLong() { - if (!MaxLengthApplies() || - !HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength) || - !mValueChanged) { + if (!mValueChanged || + !mLastValueChangeWasInteractive || + !MaxLengthApplies() || + !HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength)) { return false; } int32_t maxLength = MaxLength(); // Maxlength of -1 means parsing error. if (maxLength == -1) { return false; @@ -7187,20 +7192,17 @@ HTMLInputElement::HasBadInput() const return false; } return false; } void HTMLInputElement::UpdateTooLongValidityState() { - // TODO: this code will be re-enabled with bug 613016 and bug 613019. -#if 0 SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong()); -#endif } void HTMLInputElement::UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf) { bool notify = !mParserCreating; nsCOMPtr<nsIDOMHTMLInputElement> selection = GetSelectedRadioButton(); @@ -7730,18 +7732,20 @@ HTMLInputElement::InitializeKeyboardEven { nsTextEditorState* state = GetEditorState(); if (state) { state->InitializeKeyboardEventListeners(); } } NS_IMETHODIMP_(void) -HTMLInputElement::OnValueChanged(bool aNotify) -{ +HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) +{ + mLastValueChangeWasInteractive = aWasInteractiveUserChange; + UpdateAllValidityStates(aNotify); if (HasDirAuto()) { SetDirectionIfAuto(true, aNotify); } } NS_IMETHODIMP_(bool)
--- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -224,17 +224,17 @@ public: NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD CreateEditor() override; NS_IMETHOD_(nsIContent*) GetRootEditorNode() override; NS_IMETHOD_(Element*) CreatePlaceholderNode() override; NS_IMETHOD_(Element*) GetPlaceholderNode() override; NS_IMETHOD_(void) UpdatePlaceholderVisibility(bool aNotify) override; NS_IMETHOD_(bool) GetPlaceholderVisibility() override; NS_IMETHOD_(void) InitializeKeyboardEventListeners() override; - NS_IMETHOD_(void) OnValueChanged(bool aNotify) override; + NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override; NS_IMETHOD_(bool) HasCachedSelection() override; void GetDisplayFileName(nsAString& aFileName) const; const nsTArray<OwningFileOrDirectory>& GetFilesOrDirectoriesInternal() const { return mFilesOrDirectories; } @@ -1444,16 +1444,17 @@ protected: /** * The type of this input (<input type=...>) as an integer. * @see nsIFormControl.h (specifically NS_FORM_INPUT_*) */ uint8_t mType; nsContentUtils::AutocompleteAttrState mAutocompleteAttrState; bool mDisabledChanged : 1; bool mValueChanged : 1; + bool mLastValueChangeWasInteractive : 1; bool mCheckedChanged : 1; bool mChecked : 1; bool mHandlingSelectEvent : 1; bool mShouldInitChecked : 1; bool mParserCreating : 1; bool mInInternalActivate : 1; bool mCheckedIsToggled : 1; bool mIndeterminate : 1;
--- a/dom/html/HTMLTextAreaElement.cpp +++ b/dom/html/HTMLTextAreaElement.cpp @@ -50,16 +50,17 @@ NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER namespace mozilla { namespace dom { HTMLTextAreaElement::HTMLTextAreaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo, FromParser aFromParser) : nsGenericHTMLFormElementWithState(aNodeInfo), mValueChanged(false), + mLastValueChangeWasInteractive(false), mHandlingSelect(false), mDoneAddingChildren(!aFromParser), mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)), mDisabledChanged(false), mCanShowInvalidUI(true), mCanShowValidUI(true), mState(this) { @@ -1339,17 +1340,19 @@ HTMLTextAreaElement::SetCustomValidity(c UpdateState(true); return NS_OK; } bool HTMLTextAreaElement::IsTooLong() { - if (!HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength) || !mValueChanged) { + if (!mValueChanged || + !mLastValueChangeWasInteractive || + !HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength)) { return false; } int32_t maxLength = -1; GetMaxLength(&maxLength); // Maxlength of -1 means parsing error. if (maxLength == -1) { @@ -1370,20 +1373,17 @@ HTMLTextAreaElement::IsValueMissing() co } return IsValueEmpty(); } void HTMLTextAreaElement::UpdateTooLongValidityState() { - // TODO: this code will be re-enabled with bug 613016 and bug 613019. -#if 0 SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong()); -#endif } void HTMLTextAreaElement::UpdateValueMissingValidityState() { SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing()); } @@ -1520,18 +1520,20 @@ HTMLTextAreaElement::GetTextEditorValue( NS_IMETHODIMP_(void) HTMLTextAreaElement::InitializeKeyboardEventListeners() { mState.InitializeKeyboardEventListeners(); } NS_IMETHODIMP_(void) -HTMLTextAreaElement::OnValueChanged(bool aNotify) +HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) { + mLastValueChangeWasInteractive = aWasInteractiveUserChange; + // Update the validity state bool validBefore = IsValid(); UpdateTooLongValidityState(); UpdateValueMissingValidityState(); if (validBefore != IsValid()) { UpdateState(aNotify); }
--- a/dom/html/HTMLTextAreaElement.h +++ b/dom/html/HTMLTextAreaElement.h @@ -101,17 +101,17 @@ public: NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override; NS_IMETHOD CreateEditor() override; NS_IMETHOD_(nsIContent*) GetRootEditorNode() override; NS_IMETHOD_(Element*) CreatePlaceholderNode() override; NS_IMETHOD_(Element*) GetPlaceholderNode() override; NS_IMETHOD_(void) UpdatePlaceholderVisibility(bool aNotify) override; NS_IMETHOD_(bool) GetPlaceholderVisibility() override; NS_IMETHOD_(void) InitializeKeyboardEventListeners() override; - NS_IMETHOD_(void) OnValueChanged(bool aNotify) override; + NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override; NS_IMETHOD_(bool) HasCachedSelection() override; // nsIContent virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, bool aCompileEventHandlers) override; virtual void UnbindFromTree(bool aDeep = true, bool aNullParent = true) override; @@ -288,16 +288,18 @@ protected: // get rid of the compiler warning using nsGenericHTMLFormElementWithState::IsSingleLineTextControl; virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override; nsCOMPtr<nsIControllers> mControllers; /** Whether or not the value has changed since its default value was given. */ bool mValueChanged; + /** Whether or not the last change to the value was made interactively by the user. */ + bool mLastValueChangeWasInteractive; /** Whether or not we are already handling select event. */ bool mHandlingSelect; /** Whether or not we are done adding children (always true if not created by a parser */ bool mDoneAddingChildren; /** Whether state restoration should be inhibited in DoneAddingChildren. */ bool mInhibitStateRestoration; /** Whether our disabled state has changed from the default **/
--- a/dom/html/HTMLTrackElement.cpp +++ b/dom/html/HTMLTrackElement.cpp @@ -28,16 +28,17 @@ #include "nsIDOMEventTarget.h" #include "nsIDOMHTMLMediaElement.h" #include "nsIHttpChannel.h" #include "nsIInterfaceRequestor.h" #include "nsILoadGroup.h" #include "nsIObserver.h" #include "nsIStreamListener.h" #include "nsISupportsImpl.h" +#include "nsISupportsPrimitives.h" #include "nsMappedAttributes.h" #include "nsNetUtil.h" #include "nsRuleData.h" #include "nsStyleConsts.h" #include "nsThreadUtils.h" #include "nsVideoFrame.h" static mozilla::LazyLogModule gTrackElementLog("nsTrackElement"); @@ -68,25 +69,82 @@ static constexpr nsAttrValue::EnumTable { "metadata", static_cast<int16_t>(TextTrackKind::Metadata) }, { 0 } }; // Invalid values are treated as "metadata" in ParseAttribute, but if no value // at all is specified, it's treated as "subtitles" in GetKind static constexpr const nsAttrValue::EnumTable* kKindTableInvalidValueDefault = &kKindTable[4]; +class WindowDestroyObserver final : public nsIObserver +{ + NS_DECL_ISUPPORTS + +public: + explicit WindowDestroyObserver(HTMLTrackElement* aElement, uint64_t aWinID) + : mTrackElement(aElement) + , mInnerID(aWinID) + { + RegisterWindowDestroyObserver(); + } + void RegisterWindowDestroyObserver() + { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "inner-window-destroyed", false); + } + } + void UnRegisterWindowDestroyObserver() + { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "inner-window-destroyed"); + } + mTrackElement = nullptr; + } + NS_IMETHODIMP Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override + { + MOZ_ASSERT(NS_IsMainThread()); + if (strcmp(aTopic, "inner-window-destroyed") == 0) { + 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) { + if (mTrackElement) { + mTrackElement->NotifyShutdown(); + } + UnRegisterWindowDestroyObserver(); + } + } + return NS_OK; + } + +private: + ~WindowDestroyObserver() {}; + HTMLTrackElement* mTrackElement; + uint64_t mInnerID; +}; +NS_IMPL_ISUPPORTS(WindowDestroyObserver, nsIObserver); + /** HTMLTrackElement */ HTMLTrackElement::HTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo) : nsGenericHTMLElement(aNodeInfo) , mLoadResourceDispatched(false) + , mWindowDestroyObserver(nullptr) { } HTMLTrackElement::~HTMLTrackElement() { + if (mWindowDestroyObserver) { + mWindowDestroyObserver->UnRegisterWindowDestroyObserver(); + } + NotifyShutdown(); } NS_IMPL_ELEMENT_CLONE(HTMLTrackElement) NS_IMPL_ADDREF_INHERITED(HTMLTrackElement, Element) NS_IMPL_RELEASE_INHERITED(HTMLTrackElement, Element) NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLTrackElement, nsGenericHTMLElement, @@ -258,16 +316,20 @@ HTMLTrackElement::LoadResource() NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv)); channel->SetNotificationCallbacks(mListener); LOG(LogLevel::Debug, ("opening webvtt channel")); rv = channel->AsyncOpen2(mListener); NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv)); mChannel = channel; + nsISupports* parentObject = OwnerDoc()->GetParentObject(); + NS_ENSURE_TRUE_VOID(parentObject); + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject); + mWindowDestroyObserver = new WindowDestroyObserver(this, window->WindowID()); } nsresult HTMLTrackElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, bool aCompileEventHandlers) { @@ -366,10 +428,20 @@ HTMLTrackElement::DispatchTrustedEvent(c } void HTMLTrackElement::DropChannel() { mChannel = nullptr; } +void +HTMLTrackElement::NotifyShutdown() +{ + if (mChannel) { + mChannel->Cancel(NS_BINDING_ABORTED); + } + mChannel = nullptr; + mListener = nullptr; +} + } // namespace dom } // namespace mozilla
--- a/dom/html/HTMLTrackElement.h +++ b/dom/html/HTMLTrackElement.h @@ -19,16 +19,17 @@ class nsIContent; class nsIDocument; namespace mozilla { namespace dom { class WebVTTListener; +class WindowDestroyObserver; class HTMLTrackElement final : public nsGenericHTMLElement { public: explicit HTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo); // nsISupports NS_DECL_ISUPPORTS_INHERITED @@ -108,16 +109,18 @@ public: // Check enabling preference. static bool IsWebVTTEnabled(); void DispatchTrackRunnable(const nsString& aEventName); void DispatchTrustedEvent(const nsAString& aName); void DropChannel(); + void NotifyShutdown(); + protected: virtual ~HTMLTrackElement(); virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; void OnChannelRedirect(nsIChannel* aChannel, nsIChannel* aNewChannel, uint32_t aFlags); // Open a new channel to the HTMLTrackElement's src attribute and call // mListener's LoadResource(). @@ -131,14 +134,16 @@ protected: RefPtr<HTMLMediaElement> mMediaParent; RefPtr<WebVTTListener> mListener; void CreateTextTrack(); private: void DispatchLoadResource(); bool mLoadResourceDispatched; + + RefPtr<WindowDestroyObserver> mWindowDestroyObserver; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_HTMLTrackElement_h
--- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -2578,44 +2578,16 @@ nsGenericHTMLFormElement::IsLabelable() type == NS_FORM_OUTPUT || type == NS_FORM_SELECT || type == NS_FORM_TEXTAREA; } //---------------------------------------------------------------------- void -nsGenericHTMLElement::Blur(mozilla::ErrorResult& aError) -{ - if (!ShouldBlur(this)) { - return; - } - - nsIDocument* doc = GetComposedDoc(); - if (!doc) { - return; - } - - nsPIDOMWindowOuter* win = doc->GetWindow(); - nsIFocusManager* fm = nsFocusManager::GetFocusManager(); - if (win && fm) { - aError = fm->ClearFocus(win); - } -} - -void -nsGenericHTMLElement::Focus(ErrorResult& aError) -{ - nsIFocusManager* fm = nsFocusManager::GetFocusManager(); - if (fm) { - aError = fm->SetFocus(this, 0); - } -} - -void nsGenericHTMLElement::Click() { if (HandlingClick()) return; // Strong in case the event kills it nsCOMPtr<nsIDocument> doc = GetComposedDoc();
--- a/dom/html/nsGenericHTMLElement.h +++ b/dom/html/nsGenericHTMLElement.h @@ -46,16 +46,18 @@ typedef nsMappedAttributeElement nsGener /** * A common superclass for HTML elements */ class nsGenericHTMLElement : public nsGenericHTMLElementBase, public nsIDOMHTMLElement { public: + using Element::SetTabIndex; + using Element::Focus; explicit nsGenericHTMLElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo) : nsGenericHTMLElementBase(aNodeInfo) { NS_ASSERTION(mNodeInfo->NamespaceID() == kNameSpaceID_XHTML, "Unexpected namespace"); AddStatesSilently(NS_EVENT_STATE_LTR); SetFlags(NODE_HAS_DIRECTION_LTR); } @@ -98,30 +100,16 @@ public: { return GetBoolAttr(nsGkAtoms::hidden); } void SetHidden(bool aHidden, mozilla::ErrorResult& aError) { SetHTMLBoolAttr(nsGkAtoms::hidden, aHidden, aError); } virtual void Click(); - virtual int32_t TabIndexDefault() - { - return -1; - } - int32_t TabIndex() - { - return GetIntAttr(nsGkAtoms::tabindex, TabIndexDefault()); - } - void SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError) - { - SetHTMLIntAttr(nsGkAtoms::tabindex, aTabIndex, aError); - } - virtual void Focus(mozilla::ErrorResult& aError); - virtual void Blur(mozilla::ErrorResult& aError); void GetAccessKey(nsString& aAccessKey) { GetHTMLAttr(nsGkAtoms::accesskey, aAccessKey); } void SetAccessKey(const nsAString& aAccessKey, mozilla::ErrorResult& aError) { SetHTMLAttr(nsGkAtoms::accesskey, aAccessKey, aError); }
--- a/dom/html/nsITextControlElement.h +++ b/dom/html/nsITextControlElement.h @@ -164,17 +164,17 @@ public: /** * Returns the current expected placeholder visibility state. */ NS_IMETHOD_(bool) GetPlaceholderVisibility() = 0; /** * Callback called whenever the value is changed. */ - NS_IMETHOD_(void) OnValueChanged(bool aNotify) = 0; + NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) = 0; static const int32_t DEFAULT_COLS = 20; static const int32_t DEFAULT_ROWS = 1; static const int32_t DEFAULT_ROWS_TEXTAREA = 2; static const int32_t DEFAULT_UNDO_CAP = 1000; // wrap can be one of these three values. typedef enum {
--- a/dom/html/nsTextEditorState.cpp +++ b/dom/html/nsTextEditorState.cpp @@ -973,17 +973,18 @@ nsTextInputListener::EditAction() // Make sure we know we were changed (do NOT set this to false if there are // no undo items; JS could change the value and we'd still need to save it) if (mSetValueChanged) { frame->SetValueChanged(true); } if (!mSettingValue) { - mTxtCtrlElement->OnValueChanged(true); + mTxtCtrlElement->OnValueChanged(/* aNotify = */ true, + /* aWasInteractiveUserChange = */ true); } return NS_OK; } NS_IMETHODIMP nsTextInputListener::BeforeEditAction() { @@ -2177,17 +2178,18 @@ nsTextEditorState::SetValue(const nsAStr mBoundFrame->UpdateValueDisplay(true); } } // If we've reached the point where the root node has been created, we // can assume that it's safe to notify. ValueWasChanged(!!mRootNode); - mTextCtrlElement->OnValueChanged(!!mRootNode); + mTextCtrlElement->OnValueChanged(/* aNotify = */ !!mRootNode, + /* aWasInteractiveUserChange = */ false); return true; } void nsTextEditorState::InitializeKeyboardEventListeners() { //register key listeners
--- a/dom/html/test/mochitest.ini +++ b/dom/html/test/mochitest.ini @@ -363,16 +363,17 @@ skip-if = buildapp == 'mulet' # TC: Bug [test_bug607145.html] [test_bug610212.html] [test_bug610687.html] [test_bug611189.html] [test_bug612730.html] skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-debug(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-desktop(form control not selected/checked with synthesizeMouse, also fails on Android) [test_bug613113.html] skip-if = buildapp == 'b2g' # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer) +[test_bug613019.html] [test_bug613722.html] [test_bug613979.html] [test_bug615595.html] [test_bug615833.html] skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || os == 'mac' #TIMED_OUT # b2g(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-debug(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-desktop(form control not selected/checked with synthesizeMouse, also fails on Android) osx(bug 1275664) [test_bug617528.html] [test_bug618948.html] skip-if = buildapp == 'b2g' # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer)
new file mode 100644 --- /dev/null +++ b/dom/html/test/test_bug613019.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=613019 +--> +<head> + <title>Test for Bug 613019</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=613019">Mozilla Bug 613019</a> +<div id="content"> + <input type="text" maxlength="2" style="width:200px" value="Test"> + <textarea maxlength="2" style="width:200px">Test</textarea> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 613019 **/ + +function testInteractivityOfValidityStates(elem) { + // verify that user interactivity is necessary for validity state to apply. + is(elem.value, "Test", "Element has incorrect starting value."); + is(elem.validity.tooLong, false, "Element should not be tooLong."); + + elem.focus(); + + synthesizeKey("VK_BACK_SPACE", {}); + is(elem.value, "Tes", "Element value was not changed correctly."); + is(elem.validity.tooLong, true, "Element should still be tooLong."); + + synthesizeKey("VK_BACK_SPACE", {}); + is(elem.value, "Te", "Element value was not changed correctly."); + is(elem.validity.tooLong, false, "Element should no longer be tooLong."); + + elem.value = "Test"; + is(elem.validity.tooLong, false, + "Element should not be tooLong after non-interactive value change."); +} + +function test() { + window.getSelection().removeAllRanges(); + testInteractivityOfValidityStates(document.querySelector("input[type=text]")); + testInteractivityOfValidityStates(document.querySelector("textarea")); + SimpleTest.finish(); +} + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + setTimeout(test, 0); +}; + +</script> +</pre> +</body> +</html>
--- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -159,17 +159,17 @@ #include "mozilla/widget/AudioSession.h" #endif #ifdef MOZ_X11 #include "mozilla/X11Util.h" #endif #ifdef ACCESSIBILITY -#include "nsIAccessibilityService.h" +#include "nsAccessibilityService.h" #endif #ifndef MOZ_SIMPLEPUSH #include "mozilla/dom/PushNotifier.h" #endif #include "mozilla/dom/File.h" #include "mozilla/dom/cellbroadcast/CellBroadcastIPCService.h" @@ -2464,18 +2464,17 @@ ContentChild::RecvFlushMemory(const nsSt } bool ContentChild::RecvActivateA11y() { #ifdef ACCESSIBILITY // Start accessibility in content process if it's running in chrome // process. - nsCOMPtr<nsIAccessibilityService> accService = - services::GetAccessibilityService(); + GetOrCreateAccService(); #endif return true; } bool ContentChild::RecvGarbageCollect() { // Rebroadcast the "child-gc-request" so that workers will GC.
--- a/dom/media/WebVTTListener.cpp +++ b/dom/media/WebVTTListener.cpp @@ -84,24 +84,26 @@ WebVTTListener::AsyncOnChannelRedirect(n } return NS_OK; } NS_IMETHODIMP WebVTTListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) { + VTT_LOG("WebVTTListener::OnStartRequest\n"); return NS_OK; } NS_IMETHODIMP WebVTTListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus) { + VTT_LOG("WebVTTListener::OnStopRequest\n"); if (NS_FAILED(aStatus)) { mElement->SetReadyState(TextTrackReadyState::FailedToLoad); } // Attempt to parse any final data the parser might still have. mParserWrapper->Flush(); if (mElement->ReadyState() != TextTrackReadyState::FailedToLoad) { mElement->SetReadyState(TextTrackReadyState::Loaded); } @@ -131,16 +133,17 @@ WebVTTListener::ParseChunk(nsIInputStrea NS_IMETHODIMP WebVTTListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aStream, uint64_t aOffset, uint32_t aCount) { + VTT_LOG("WebVTTListener::OnDataAvailable\n"); uint32_t count = aCount; while (count > 0) { uint32_t read; nsresult rv = aStream->ReadSegments(ParseChunk, this, count, &read); NS_ENSURE_SUCCESS(rv, rv); if (!read) { return NS_ERROR_FAILURE; }
--- a/dom/media/platforms/wmf/DXVA2Manager.cpp +++ b/dom/media/platforms/wmf/DXVA2Manager.cpp @@ -299,17 +299,17 @@ D3D9DXVA2Manager::Init(nsACString& aFail } // Create D3D9DeviceEx. We pass null HWNDs here even though the documentation // suggests that one of them should not be. At this point in time Chromium // does the same thing for video acceleration. D3DPRESENT_PARAMETERS params = {0}; params.BackBufferWidth = 1; params.BackBufferHeight = 1; - params.BackBufferFormat = D3DFMT_UNKNOWN; + params.BackBufferFormat = D3DFMT_A8R8G8B8; params.BackBufferCount = 1; params.SwapEffect = D3DSWAPEFFECT_DISCARD; params.hDeviceWindow = nullptr; params.Windowed = TRUE; params.Flags = D3DPRESENTFLAG_VIDEO; RefPtr<IDirect3DDevice9Ex> device; hr = d3d9Ex->CreateDeviceEx(D3DADAPTER_DEFAULT,
--- a/dom/media/tests/mochitest/mochitest.ini +++ b/dom/media/tests/mochitest/mochitest.ini @@ -20,17 +20,17 @@ support-files = !/dom/media/test/320x240.ogv !/dom/media/test/r11025_s16_c1.wav !/dom/media/test/bug461281.ogg !/dom/media/test/seek.webm !/dom/media/test/gizmo.mp4 [test_a_noOp.html] [test_dataChannel_basicAudio.html] -skip-if = toolkit == 'gonk' || buildapp == 'mulet' # Bug 962984 for debug, bug 963244 for opt +skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # Bug 962984 for debug, bug 963244 for opt [test_dataChannel_basicAudioVideo.html] skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator) [test_dataChannel_basicAudioVideoNoBundle.html] skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) FAILS WHEN RUN MANUALLY ON AWS, android(Bug 1189784, timeouts on 4.3 emulator) [test_dataChannel_basicAudioVideoCombined.html] skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator) [test_dataChannel_basicDataOnly.html] [test_dataChannel_basicVideo.html] @@ -80,17 +80,17 @@ skip-if = (toolkit == 'gonk' || buildapp [test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html] [test_getUserMedia_stopVideoStream.html] [test_getUserMedia_stopVideoStreamWithFollowupVideo.html] [test_getUserMedia_trackEnded.html] [test_getUserMedia_peerIdentity.html] skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 1021776, too --ing slow on b2g) [test_peerConnection_addIceCandidate.html] [test_peerConnection_addtrack_removetrack_events.html] -skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator) +skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator) [test_peerConnection_basicAudio.html] skip-if = toolkit == 'gonk' # B2G emulator is too slow to handle a two-way audio call reliably [test_peerConnection_basicAudioNATSrflx.html] skip-if = toolkit == 'gonk' || toolkit == 'android' # B2G emulator is too slow to handle a two-way audio call reliably, websockets don't work on android (bug 1266217) [test_peerConnection_basicAudioNATRelay.html] skip-if = toolkit == 'gonk' || toolkit == 'android' # B2G emulator is too slow to handle a two-way audio call reliably, websockets don't work on android (bug 1266217) [test_peerConnection_basicAudioNATRelayTCP.html] skip-if = toolkit == 'gonk' || toolkit == 'android' # B2G emulator is too slow to handle a two-way audio call reliably, websockets don't work on android (bug 1266217) @@ -199,19 +199,19 @@ skip-if = toolkit == 'gonk' || buildapp skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator) [test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html] [test_peerConnection_setRemoteAnswerInStable.html] [test_peerConnection_setRemoteOfferInHaveLocalOffer.html] [test_peerConnection_throwInCallbacks.html] skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) [test_peerConnection_toJSON.html] [test_peerConnection_trackDisabling_clones.html] -skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator) +skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator) [test_peerConnection_trackDisabling.html] -skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) +skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator) [test_peerConnection_twoAudioStreams.html] skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g (Bug 1059867), android(Bug 1189784, timeouts on 4.3 emulator) [test_peerConnection_twoAudioTracksInOneStream.html] skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g (Bug 1059867), android(Bug 1189784, timeouts on 4.3 emulator) [test_peerConnection_twoAudioVideoStreams.html] # b2g(Bug 960442, video support for WebRTC is disabled on b2g), Bug 1171255 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator) skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (os == 'linux' && debug && e10s) || android_version == '18' [test_peerConnection_twoAudioVideoStreamsCombined.html] @@ -222,23 +222,23 @@ skip-if = toolkit == 'gonk' || buildapp skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' [test_peerConnection_twoVideoTracksInOneStream.html] # b2g(Bug 960442, video support for WebRTC is disabled on b2g), Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator) skip-if = toolkit == 'gonk' || buildapp == 'mulet' || os == "android" [test_peerConnection_addAudioTrackToExistingVideoStream.html] # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator) skip-if = toolkit == 'gonk' || (android_version == '18' && debug) [test_peerConnection_addSecondAudioStream.html] -skip-if = toolkit == 'gonk' # B2G emulator is too slow to finish a renegotiation test in under 5 minutes +skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # emulator is too slow to finish a renegotiation test in under 5 minutes [test_peerConnection_answererAddSecondAudioStream.html] -skip-if = toolkit == 'gonk' # B2G emulator is too slow to finish a renegotiation test in under 5 minutes +skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # emulator is too slow to finish a renegotiation test in under 5 minutes [test_peerConnection_removeAudioTrack.html] -skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator) +skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator) [test_peerConnection_removeThenAddAudioTrack.html] -skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator) +skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator) [test_peerConnection_addSecondVideoStream.html] # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator) skip-if = toolkit == 'gonk' || android_version == '18' [test_peerConnection_removeVideoTrack.html] # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator) skip-if = toolkit == 'gonk' || (android_version == '18' && debug) [test_peerConnection_removeThenAddVideoTrack.html] # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator) @@ -252,37 +252,37 @@ skip-if = toolkit == 'gonk' || (android_ skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator) [test_peerConnection_addSecondVideoStreamNoBundle.html] # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator) skip-if = toolkit == 'gonk' || android_version == '18' [test_peerConnection_removeThenAddVideoTrackNoBundle.html] # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator) skip-if = toolkit == 'gonk' || android_version == '18' [test_peerConnection_addDataChannel.html] -skip-if = toolkit == 'gonk' # B2G emulator seems to be so slow that DTLS cannot establish properly +skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # emulator seems to be so slow that DTLS cannot establish properly, android(bug 1240256, intermittent ICE failures starting w/bug 1232082, possibly from timeout) [test_peerConnection_addDataChannelNoBundle.html] # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator) skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # b2g(emulator seems to be so slow that DTLS cannot establish properly), android(bug 1240256, intermittent ICE failures starting w/bug 1232082, possibly from timeout) [test_peerConnection_verifyAudioAfterRenegotiation.html] -skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator) +skip-if = toolkit == 'gonk' || android_version == '18' # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator) [test_peerConnection_verifyVideoAfterRenegotiation.html] # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator) skip-if = toolkit == 'gonk' || android_version == '18' [test_peerConnection_audioRenegotiationInactiveAnswer.html] # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator) skip-if = toolkit == 'gonk' || android_version == '18' [test_peerConnection_videoRenegotiationInactiveAnswer.html] # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator) skip-if = toolkit == 'gonk' || android_version == '18' [test_peerConnection_webAudio.html] tags = webaudio webrtc -skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867) +skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g (Bug 1059867), android(Bug 1189784, timeouts on 4.3 emulator) [test_peerConnection_localRollback.html] skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867) [test_peerConnection_localReofferRollback.html] skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867) [test_peerConnection_remoteRollback.html] skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # b2g (Bug 1059867), android(Bug 1189784, timeouts on 4.3 emulator) [test_peerConnection_remoteReofferRollback.html] -skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867) +skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g (Bug 1059867), android(Bug 1189784, timeouts on 4.3 emulator) [test_selftest.html] # Bug 1227781: Crash with bogus TURN server. [test_peerConnection_bug1227781.html]
--- a/dom/media/tests/mochitest/pc.js +++ b/dom/media/tests/mochitest/pc.js @@ -1455,20 +1455,20 @@ PeerConnectionWrapper.prototype = { : "packetsReceived"]; info("Track " + track.id + " has " + nrPackets + " " + rtp.type + " RTP packets."); return nrPackets > 0; }; info("Checking RTP packet flow for track " + track.id); - var retry = () => this._pc.getStats(track) + var retry = (delay) => this._pc.getStats(track) .then(stats => hasFlow(stats)? ok(true, "RTP flowing for track " + track.id) : - wait(200).then(retry)); - return retry(); + wait(delay).then(retry(1000))); + return retry(200); }, /** * Wait for presence of video flow on all media elements and rtp flow on * all sending and receiving track involved in this test. * * @returns {Promise} * A promise that resolves when media flows for all elements and tracks
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp +++ b/dom/media/webrtc/MediaEngineWebRTC.cpp @@ -120,19 +120,16 @@ MediaEngineWebRTC::MediaEngineWebRTC(Med } #else #ifdef MOZ_WIDGET_GONK AsyncLatencyLogger::Get()->AddRef(); #endif #endif // XXX gFarendObserver = new AudioOutputObserver(); - - NS_NewNamedThread("AudioGUM", getter_AddRefs(mThread)); - MOZ_ASSERT(mThread); } void MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource, nsTArray<RefPtr<MediaEngineVideoSource> >* aVSources) { // We spawn threads to handle gUM runnables, so we must protect the member vars MutexAutoLock lock(mMutex); @@ -404,17 +401,17 @@ MediaEngineWebRTC::EnumerateAudioDevices if (SupportsDuplex()) { // The platform_supports_full_duplex. // For cubeb, it has state (the selected ID) // XXX just use the uniqueID for cubeb and support it everywhere, and get rid of this // XXX Small window where the device list/index could change! audioinput = new mozilla::AudioInputCubeb(mVoiceEngine, i); } - aSource = new MediaEngineWebRTCMicrophoneSource(mThread, mVoiceEngine, audioinput, + aSource = new MediaEngineWebRTCMicrophoneSource(mVoiceEngine, audioinput, i, deviceName, uniqueId); mAudioSources.Put(uuid, aSource); // Hashtable takes ownership. aASources->AppendElement(aSource); } } } void @@ -445,16 +442,11 @@ MediaEngineWebRTC::Shutdown() mVoiceEngine->SetTraceCallback(nullptr); webrtc::VoiceEngine::Delete(mVoiceEngine); } mVoiceEngine = nullptr; mozilla::camera::Shutdown(); AudioInputCubeb::CleanupGlobalData(); - - if (mThread) { - mThread->Shutdown(); - mThread = nullptr; - } } }
--- a/dom/media/webrtc/MediaEngineWebRTC.h +++ b/dom/media/webrtc/MediaEngineWebRTC.h @@ -414,18 +414,17 @@ private: RefPtr<MediaEngineAudioSource> mAudioSource; }; class MediaEngineWebRTCMicrophoneSource : public MediaEngineAudioSource, public webrtc::VoEMediaProcess { typedef MediaEngineAudioSource Super; public: - MediaEngineWebRTCMicrophoneSource(nsIThread* aThread, - webrtc::VoiceEngine* aVoiceEnginePtr, + MediaEngineWebRTCMicrophoneSource(webrtc::VoiceEngine* aVoiceEnginePtr, mozilla::AudioInput* aAudioInput, int aIndex, const char* name, const char* uuid); void GetName(nsAString& aName) const override; void GetUUID(nsACString& aUUID) const override; @@ -538,17 +537,16 @@ private: // mMonitor protects mSources[] and mPrinicpalIds[] access/changes, and // transitions of mState from kStarted to kStopped (which are combined with // EndTrack()). mSources[] and mPrincipalHandles[] are accessed from webrtc // threads. Monitor mMonitor; nsTArray<RefPtr<SourceMediaStream>> mSources; nsTArray<PrincipalHandle> mPrincipalHandles; // Maps to mSources. - nsCOMPtr<nsIThread> mThread; int mCapIndex; int mChannel; TrackID mTrackID; bool mStarted; nsString mDeviceName; nsCString mDeviceUUID;
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp +++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp @@ -179,27 +179,25 @@ AudioOutputObserver::InsertFarEnd(const mSaved = nullptr; mSamplesSaved = 0; } } } } MediaEngineWebRTCMicrophoneSource::MediaEngineWebRTCMicrophoneSource( - nsIThread* aThread, webrtc::VoiceEngine* aVoiceEnginePtr, mozilla::AudioInput* aAudioInput, int aIndex, const char* name, const char* uuid) : MediaEngineAudioSource(kReleased) , mVoiceEngine(aVoiceEnginePtr) , mAudioInput(aAudioInput) , mMonitor("WebRTCMic.Monitor") - , mThread(aThread) , mCapIndex(aIndex) , mChannel(-1) , mStarted(false) , mSampleFrequency(MediaEngine::DEFAULT_SAMPLE_RATE) , mPlayoutDelay(0) , mNullTransport(nullptr) , mSkipProcessing(false) { @@ -611,21 +609,17 @@ MediaEngineWebRTCMicrophoneSource::Inser // XXX Bug 971528 - Support stereo capture in gUM MOZ_ASSERT(aChannels == 1, "GraphDriver only supports us stereo audio for now"); channels.AppendElement(static_cast<T*>(buffer->Data())); segment->AppendFrames(buffer.forget(), channels, aFrames, mPrincipalHandles[i]); segment->GetStartTime(insertTime); - RUN_ON_THREAD(mThread, - WrapRunnable(mSources[i], &SourceMediaStream::AppendToTrack, - mTrackID, segment, - static_cast<AudioSegment*>(nullptr)), - NS_DISPATCH_NORMAL); + mSources[i]->AppendToTrack(mTrackID, segment); } } // Called back on GraphDriver thread! // Note this can be called back after ::Shutdown() void MediaEngineWebRTCMicrophoneSource::NotifyInputData(MediaStreamGraph* aGraph, const AudioDataValue* aBuffer,
--- a/dom/plugins/ipc/PluginInstanceParent.cpp +++ b/dom/plugins/ipc/PluginInstanceParent.cpp @@ -949,18 +949,19 @@ PluginInstanceParent::RecvShow(const NPR if (surface) { // Notify the cairo backend that this surface has changed behind // its back. gfxRect ur(updatedRect.left, updatedRect.top, updatedRect.right - updatedRect.left, updatedRect.bottom - updatedRect.top); surface->MarkDirty(ur); + bool isPlugin = true; RefPtr<gfx::SourceSurface> sourceSurface = - gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface); + gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface, isPlugin); RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), sourceSurface); AutoTArray<ImageContainer::NonOwningImage,1> imageList; imageList.AppendElement( ImageContainer::NonOwningImage(image)); ImageContainer *container = GetImageContainer(); container->SetCurrentImages(imageList);
--- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -987,29 +987,29 @@ Promise::PerformMicroTaskCheckpoint() // On the main thread, we always use the main promise micro task queue. std::queue<nsCOMPtr<nsIRunnable>>& microtaskQueue = runtime->GetPromiseMicroTaskQueue(); if (microtaskQueue.empty()) { return false; } - AutoSafeJSContext cx; + AutoSlowOperation aso; do { nsCOMPtr<nsIRunnable> runnable = microtaskQueue.front().forget(); MOZ_ASSERT(runnable); // This function can re-enter, so we remove the element before calling. microtaskQueue.pop(); nsresult rv = runnable->Run(); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } - JS_CheckForInterrupt(cx); + aso.CheckForInterrupt(); runtime->AfterProcessMicrotask(); } while (!microtaskQueue.empty()); return true; } void Promise::PerformWorkerMicroTaskCheckpoint()
--- a/dom/security/test/cors/test_CrossSiteXHR.html +++ b/dom/security/test/cors/test_CrossSiteXHR.html @@ -754,28 +754,30 @@ function runTest() { is(res.didFail, true, "should have failed in test for " + test.toSource()); is(res.status, 0, "wrong status in test for " + test.toSource()); is(res.statusText, "", "wrong status text for " + test.toSource()); is(res.responseXML, null, "wrong responseXML in test for " + test.toSource()); is(res.responseText, "", "wrong responseText in test for " + test.toSource()); + var expectedProgressCount = 0; if (!res.sendThrew) { if (test.username) { + expectedProgressCount = 1; is(res.events.join(","), "opening,rs1,sending,loadstart,rs4,error,loadend", "wrong events in test for " + test.toSource()); } else { is(res.events.join(","), "opening,rs1,sending,loadstart,rs2,rs4,error,loadend", "wrong events in test for " + test.toSource()); } } - is(res.progressEvents, 0, + is(res.progressEvents, expectedProgressCount, "wrong events in test for " + test.toSource()); if (test.responseHeaders) { for (header in test.responseHeaders) { is(res.responseHeaders[header], null, "wrong response header (" + header + ") in test for " + test.toSource()); } }
--- a/dom/svg/SVGAElement.cpp +++ b/dom/svg/SVGAElement.cpp @@ -189,16 +189,19 @@ SVGAElement::IsFocusableInternal(int32_t { nsCOMPtr<nsIURI> uri; if (IsLink(getter_AddRefs(uri))) { if (aTabIndex) { *aTabIndex = ((sTabFocusModel & eTabFocus_linksMask) == 0 ? -1 : 0); } return true; } + if (nsSVGElement::IsFocusableInternal(aTabIndex, aWithMouse)) { + return true; + } if (aTabIndex) { *aTabIndex = -1; } return false; }
--- a/dom/svg/nsSVGElement.cpp +++ b/dom/svg/nsSVGElement.cpp @@ -605,16 +605,19 @@ nsSVGElement::ParseAttribute(int32_t aNa rv = transformList->SetBaseValueString(aValue); if (NS_FAILED(rv)) { transformList->ClearBaseValue(); } else { aResult.SetTo(transformList->GetBaseValue(), &aValue); didSetResult = true; } foundMatch = true; + } else if (aAttribute == nsGkAtoms::tabindex) { + didSetResult = aResult.ParseIntValue(aValue); + foundMatch = true; } } if (aAttribute == nsGkAtoms::_class) { mClassAttribute.SetBaseValue(aValue, this, false); aResult.ParseAtomArray(aValue); return true; } @@ -1115,16 +1118,29 @@ nsSVGElement::GetViewportElement() } already_AddRefed<SVGAnimatedString> nsSVGElement::ClassName() { return mClassAttribute.ToDOMAnimatedString(this); } +bool +nsSVGElement::IsFocusableInternal(int32_t* aTabIndex, bool) +{ + int32_t index = TabIndex(); + + if (index == -1) { + return false; + } + + *aTabIndex = index; + return true; +} + //------------------------------------------------------------------------ // Helper class: MappedAttrParser, for parsing values of mapped attributes namespace { class MOZ_STACK_CLASS MappedAttrParser { public: MappedAttrParser(css::Loader* aLoader,
--- a/dom/svg/nsSVGElement.h +++ b/dom/svg/nsSVGElement.h @@ -312,16 +312,18 @@ public: virtual void ClearAnyCachedPath() {} virtual nsIDOMNode* AsDOMNode() final override { return this; } virtual bool IsTransformable() { return false; } // WebIDL mozilla::dom::SVGSVGElement* GetOwnerSVGElement(); nsSVGElement* GetViewportElement(); already_AddRefed<mozilla::dom::SVGAnimatedString> ClassName(); + virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override; + protected: virtual JSObject* WrapNode(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override; #ifdef DEBUG // We define BeforeSetAttr here and mark it final to ensure it is NOT used // by SVG elements. // This is because we're not currently passing the correct value for aValue to // BeforeSetAttr since it would involve allocating extra SVG value types.
--- a/dom/svg/test/mochitest.ini +++ b/dom/svg/test/mochitest.ini @@ -81,16 +81,17 @@ skip-if = android_version == '18' # bug [test_SVGStringList.xhtml] [test_SVGStyleElement.xhtml] [test_SVGTransformListAddition.xhtml] [test_SVGTransformList.xhtml] [test_SVGUnitTypes.html] [test_SVGxxxListIndexing.xhtml] [test_SVGxxxList.xhtml] [test_switch.xhtml] +[test_tabindex.html] [test_tearoff_with_cc.html] support-files = tearoff_with_cc_helper.html [test_text_2.html] [test_text_dirty.html] [test_text.html] [test_text_lengthAdjust.html] [test_text_scaled.html] [test_text_selection.html]
new file mode 100644 --- /dev/null +++ b/dom/svg/test/test_tabindex.html @@ -0,0 +1,68 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SVG tabIndex - Bug 778654</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> +</head> +<body> +<svg xmlns="http://www.w3.org/2000/svg" overflow="visible"> + <foreignObject id="f" x="0" y="0" width="200" height="60" tabindex="0"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <p>Here is a paragraph that requires word wrap</p> + </body> + </foreignObject> + <rect id="r" x="0" y="70" width="100" height="100" fill="yellow" tabIndex="1"/> + <text id="t" x="0" y="200" tabindex="2"> + This is SVG text + </text> +</svg> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +function main() +{ + var f = document.getElementById('f'); + var r = document.getElementById('r'); + var t = document.getElementById('t'); + + try { + // Step 1: Checking by assigning tabIndex + is(f.tabIndex, 0, "foreignObject initial tabIndex"); + f.tabIndex = 1; + is(f.tabIndex, 1, "foreignObject tabIndex is set to 1"); + + is(r.tabIndex, 1, "rect initial tabIndex"); + r.tabIndex = 2; + is(r.tabIndex, 2, "rect tabIndex is set to 2"); + + is(t.tabIndex, 2, "text initial tabIndex"); + t.tabIndex = 3; + is(t.tabIndex, 3, "text is set to 3"); + + // Step 2: Checking by triggering TAB event + is(document.activeElement.tabIndex, -1, "In the beginning, the active element tabindex is -1"); + + synthesizeKey("VK_TAB", {}); + is(document.activeElement.tabIndex, 1, "The active element tabindex is 1"); + + synthesizeKey("VK_TAB", {}); + is(document.activeElement.tabIndex, 2, "The active element tabindex is 2"); + + synthesizeKey("VK_TAB", {}); + is(document.activeElement.tabIndex, 3, "The active element tabindex is 3"); + } catch(e) { + ok(false, "Got unexpected exception" + e); + } + + SimpleTest.finish(); +} + +window.addEventListener("load", main, false); +</script> +</pre> +</body> +</html>
--- a/dom/webidl/SVGElement.webidl +++ b/dom/webidl/SVGElement.webidl @@ -16,16 +16,21 @@ interface SVGElement : Element { [Constant] readonly attribute SVGAnimatedString className; [PutForwards=cssText, Constant] readonly attribute CSSStyleDeclaration style; readonly attribute SVGSVGElement? ownerSVGElement; readonly attribute SVGElement? viewportElement; - attribute EventHandler oncopy; - attribute EventHandler oncut; - attribute EventHandler onpaste; + attribute EventHandler oncopy; + attribute EventHandler oncut; + attribute EventHandler onpaste; + + [SetterThrows, Pure] + attribute long tabIndex; + [Throws] void focus(); + [Throws] void blur(); }; SVGElement implements GlobalEventHandlers; SVGElement implements TouchEventHandlers; SVGElement implements OnErrorEventHandlerForNodes;
--- a/dom/workers/test/bug1063538_worker.js +++ b/dom/workers/test/bug1063538_worker.js @@ -1,18 +1,25 @@ /** * Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ var gJar = "jar:http://example.org/tests/dom/base/test/file_bug945152.jar!/data_big.txt"; var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true}); +var progressFired = false; + +xhr.onloadend = function(e) { + postMessage({type: 'finish', progressFired: progressFired }); + self.close(); +}; xhr.onprogress = function(e) { + if (e.loaded > 0) { + progressFired = true; + } xhr.abort(); - postMessage({type: 'finish' }); - self.close(); }; onmessage = function(e) { xhr.open("GET", gJar, true); xhr.send(); }
--- a/dom/workers/test/test_bug1063538.html +++ b/dom/workers/test/test_bug1063538.html @@ -22,17 +22,17 @@ https://bugzilla.mozilla.org/show_bug.cg <pre id="test"> <script type="application/javascript"> function runTest() { var worker = new Worker("bug1063538_worker.js"); worker.onmessage = function(e) { if (e.data.type == 'finish') { - ok(true, "Testing done.\n"); + ok(e.data.progressFired, "Progress was fired."); SimpleTest.finish(); } }; worker.postMessage(true); } SimpleTest.waitForExplicitFinish();
--- a/dom/xhr/XMLHttpRequestMainThread.cpp +++ b/dom/xhr/XMLHttpRequestMainThread.cpp @@ -168,17 +168,17 @@ XMLHttpRequestMainThread::XMLHttpRequest mFlagTimedOut(false), mFlagDeleted(false), mFlagSend(false), mUploadTransferred(0), mUploadTotal(0), mUploadComplete(true), mProgressSinceLastProgressEvent(false), mRequestSentTime(0), mTimeoutMilliseconds(0), mErrorLoad(false), mWaitingForOnStopRequest(false), mProgressTimerIsActive(false), mIsHtml(false), mWarnAboutSyncHtml(false), - mLoadLengthComputable(false), mLoadTotal(0), + mLoadTotal(0), mIsSystem(false), mIsAnon(false), mFirstStartRequestSeen(false), mInLoadProgressEvent(false), mResultJSON(JS::UndefinedValue()), mResultArrayBuffer(nullptr), mIsMappedArrayBuffer(false), mXPCOMifier(nullptr) @@ -1013,38 +1013,37 @@ XMLHttpRequestMainThread::CloseRequest() } } void XMLHttpRequestMainThread::CloseRequestWithError(const ProgressEventType aType) { CloseRequest(); - uint32_t responseLength = mResponseBody.Length(); ResetResponse(); // If we're in the destructor, don't risk dispatching an event. if (mFlagDeleted) { mFlagSyncLooping = false; return; } if (mState != State::unsent && !(mState == State::opened && !mFlagSend) && mState != State::done) { ChangeState(State::done, true); if (!mFlagSyncLooping) { - DispatchProgressEvent(this, aType, mLoadLengthComputable, responseLength, - mLoadTotal); if (mUpload && !mUploadComplete) { mUploadComplete = true; - DispatchProgressEvent(mUpload, aType, true, mUploadTransferred, - mUploadTotal); + DispatchProgressEvent(mUpload, ProgressEventType::progress, 0, 0); + DispatchProgressEvent(mUpload, aType, 0, 0); } + DispatchProgressEvent(this, ProgressEventType::progress, 0, 0); + DispatchProgressEvent(this, aType, 0, 0); } } // The ChangeState call above calls onreadystatechange handlers which // if they load a new url will cause XMLHttpRequestMainThread::Open to clear // the abort state bit. If this occurs we're not uninitialized (bug 361773). if (mFlagAborted) { ChangeState(State::unsent, false); // IE seems to do it @@ -1291,46 +1290,72 @@ XMLHttpRequestMainThread::FireReadystate event->SetTrusted(true); DispatchDOMEvent(nullptr, event, nullptr, nullptr); return NS_OK; } void XMLHttpRequestMainThread::DispatchProgressEvent(DOMEventTargetHelper* aTarget, const ProgressEventType aType, - bool aLengthComputable, int64_t aLoaded, int64_t aTotal) { NS_ASSERTION(aTarget, "null target"); if (NS_FAILED(CheckInnerWindowCorrectness()) || (!AllowUploadProgress() && aTarget == mUpload)) { return; } + // If blocked by CORS, zero-out the stats on progress events + // and never fire "progress" or "load" events at all. + if (IsDeniedCrossSiteCORSRequest()) { + if (aType == ProgressEventType::progress || + aType == ProgressEventType::load) { + return; + } + aLoaded = 0; + aTotal = 0; + } + + if (aType == ProgressEventType::progress) { + mInLoadProgressEvent = true; + } + ProgressEventInit init; init.mBubbles = false; init.mCancelable = false; - init.mLengthComputable = aLengthComputable; + init.mLengthComputable = aTotal != 0; // XHR spec step 6.1 init.mLoaded = aLoaded; init.mTotal = (aTotal == -1) ? 0 : aTotal; const nsAString& typeString = ProgressEventTypeStrings[(uint8_t)aType]; RefPtr<ProgressEvent> event = ProgressEvent::Constructor(aTarget, typeString, init); event->SetTrusted(true); aTarget->DispatchDOMEvent(nullptr, event, nullptr, nullptr); + if (aType == ProgressEventType::progress) { + mInLoadProgressEvent = false; + + // clear chunked responses after every progress event + if (mResponseType == XMLHttpRequestResponseType::Moz_chunked_text || + mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer) { + mResponseBody.Truncate(); + mResponseText.Truncate(); + mResultArrayBuffer = nullptr; + mArrayBufferBuilder.reset(); + } + } + // If we're sending a load, error, timeout or abort event, then // also dispatch the subsequent loadend event. if (aType == ProgressEventType::load || aType == ProgressEventType::error || aType == ProgressEventType::timeout || aType == ProgressEventType::abort) { - DispatchProgressEvent(aTarget, ProgressEventType::loadend, - aLengthComputable, aLoaded, aTotal); + DispatchProgressEvent(aTarget, ProgressEventType::loadend, aLoaded, aTotal); } } already_AddRefed<nsIHttpChannel> XMLHttpRequestMainThread::GetCurrentHttpChannel() { nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel); return httpChannel.forget(); @@ -1343,16 +1368,23 @@ XMLHttpRequestMainThread::GetCurrentJARC return appChannel.forget(); } bool XMLHttpRequestMainThread::IsSystemXHR() const { return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal); } + +bool +XMLHttpRequestMainThread::InUploadPhase() const +{ + // We're in the upload phase while our state is State::opened. + return mState == State::opened; +} NS_IMETHODIMP XMLHttpRequestMainThread::Open(const nsACString& aMethod, const nsACString& aUrl, bool aAsync, const nsAString& aUsername, const nsAString& aPassword, uint8_t optional_argc) { Optional<bool> async; if (!optional_argc) { @@ -1675,17 +1707,19 @@ XMLHttpRequestMainThread::OnDataAvailabl ChangeState(State::loading); return request->Cancel(NS_OK); } mDataAvailable += totalRead; ChangeState(State::loading); - MaybeDispatchProgressEvents(false); + if (!mFlagSynchronous && !mProgressTimerIsActive) { + StartProgressEventTimer(); + } return NS_OK; } NS_IMETHODIMP XMLHttpRequestMainThread::OnStartRequest(nsIRequest *request, nsISupports *ctxt) { PROFILER_LABEL("XMLHttpRequestMainThread", "OnStartRequest", @@ -1722,30 +1756,32 @@ XMLHttpRequestMainThread::OnStartRequest nsCOMPtr<nsIChannel> channel(do_QueryInterface(request)); NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED); nsresult status; request->GetStatus(&status); mErrorLoad = mErrorLoad || NS_FAILED(status); + // Upload phase is now over. If we were uploading anything, + // stop the timer and fire any final progress events. if (mUpload && !mUploadComplete && !mErrorLoad && !mFlagSynchronous) { - if (mProgressTimerIsActive) { - mProgressTimerIsActive = false; - mProgressNotifier->Cancel(); + StopProgressEventTimer(); + + mUploadTransferred = mUploadTotal; + + if (mProgressSinceLastProgressEvent) { + DispatchProgressEvent(mUpload, ProgressEventType::progress, + mUploadTransferred, mUploadTotal); + mProgressSinceLastProgressEvent = false; } - if (mUploadTransferred < mUploadTotal) { - mUploadTransferred = mUploadTotal; - mProgressSinceLastProgressEvent = true; - mUploadLengthComputable = true; - MaybeDispatchProgressEvents(true); - } + mUploadComplete = true; DispatchProgressEvent(mUpload, ProgressEventType::load, - true, mUploadTotal, mUploadTotal); + mUploadTotal, mUploadTotal); } mContext = ctxt; mFlagParseBody = true; ChangeState(State::headers_received); ResetResponse(); @@ -1919,20 +1955,18 @@ XMLHttpRequestMainThread::OnStartRequest // the spec requires the response document.referrer to be the empty string mResponseXML->SetReferrer(NS_LITERAL_CSTRING("")); mXMLParserStreamListener = listener; rv = mXMLParserStreamListener->OnStartRequest(request, ctxt); NS_ENSURE_SUCCESS(rv, rv); } - // We won't get any progress events anyway if we didn't have progress - // events when starting the request - so maybe no need to start timer here. - if (NS_SUCCEEDED(rv) && !mFlagSynchronous && - HasListenersFor(nsGkAtoms::onprogress)) { + // Download phase beginning; start the progress event timer if necessary. + if (NS_SUCCEEDED(rv) && HasListenersFor(nsGkAtoms::onprogress)) { StartProgressEventTimer(); } return NS_OK; } NS_IMETHODIMP XMLHttpRequestMainThread::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) @@ -1965,26 +1999,24 @@ XMLHttpRequestMainThread::OnStopRequest( // Is this good enough here? if (mXMLParserStreamListener && mFlagParseBody) { mXMLParserStreamListener->OnStopRequest(request, ctxt, status); } mXMLParserStreamListener = nullptr; mContext = nullptr; - // If we're received data since the last progress event, make sure to fire - // an event for it, except in the HTML case, defer the last progress event - // until the parser is done. - if (!mIsHtml) { - MaybeDispatchProgressEvents(true); - } - if (NS_SUCCEEDED(status) && + (mResponseType == XMLHttpRequestResponseType::_empty || + mResponseType == XMLHttpRequestResponseType::Text)) { + mLoadTotal = mResponseBody.Length(); + } else if (NS_SUCCEEDED(status) && (mResponseType == XMLHttpRequestResponseType::Blob || mResponseType == XMLHttpRequestResponseType::Moz_blob)) { + ErrorResult rv; if (!mDOMBlob) { CreateDOMBlob(request); } if (mDOMBlob) { mResponseBlob = mDOMBlob; mDOMBlob = nullptr; } else { // mBlobSet can be null if the channel is non-file non-cacheable @@ -1992,33 +2024,39 @@ XMLHttpRequestMainThread::OnStopRequest( if (!mBlobSet) { mBlobSet = new BlobSet(); } // Smaller files may be written in cache map instead of separate files. // Also, no-store response cannot be written in persistent cache. nsAutoCString contentType; mChannel->GetContentType(contentType); - ErrorResult rv; mResponseBlob = mBlobSet->GetBlobInternal(GetOwner(), contentType, rv); mBlobSet = nullptr; if (NS_WARN_IF(rv.Failed())) { return rv.StealNSResult(); } } + + mLoadTotal = mResponseBlob->GetSize(rv); + if (NS_WARN_IF(rv.Failed())) { + status = rv.StealNSResult(); + } + NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty"); NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty"); } else if (NS_SUCCEEDED(status) && ((mResponseType == XMLHttpRequestResponseType::Arraybuffer && !mIsMappedArrayBuffer) || mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer)) { // set the capacity down to the actual length, to realloc back // down to the actual size - if (!mArrayBufferBuilder.setCapacity(mArrayBufferBuilder.length())) { + mLoadTotal = mArrayBufferBuilder.length(); + if (!mArrayBufferBuilder.setCapacity(mLoadTotal)) { // this should never happen! status = NS_ERROR_UNEXPECTED; } } nsCOMPtr<nsIChannel> channel(do_QueryInterface(request)); NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED); @@ -2040,67 +2078,96 @@ XMLHttpRequestMainThread::OnStopRequest( // If we're uninitialized at this point, we encountered an error // earlier and listeners have already been notified. Also we do // not want to do this if we already completed. if (mState == State::unsent || mState == State::done) { return NS_OK; } if (!mResponseXML) { + mFlagParseBody = false; ChangeStateToDone(); return NS_OK; } + if (mIsHtml) { NS_ASSERTION(!mFlagSyncLooping, "We weren't supposed to support HTML parsing with XHR!"); nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(mResponseXML); EventListenerManager* manager = eventTarget->GetOrCreateListenerManager(); manager->AddEventListenerByType(new nsXHRParseEndListener(this), kLiteralString_DOMContentLoaded, TrustedEventsAtSystemGroupBubble()); return NS_OK; + } else { + mFlagParseBody = false; } + // We might have been sent non-XML data. If that was the case, // we should null out the document member. The idea in this // check here is that if there is no document element it is not // an XML document. We might need a fancier check... if (!mResponseXML->GetRootElement()) { mResponseXML = nullptr; } ChangeStateToDone(); return NS_OK; } void +XMLHttpRequestMainThread::OnBodyParseEnd() +{ + mFlagParseBody = false; + ChangeStateToDone(); +} + +void XMLHttpRequestMainThread::ChangeStateToDone() { - if (mIsHtml) { - // In the HTML case, this has to be deferred, because the parser doesn't - // do it's job synchronously. - MaybeDispatchProgressEvents(true); - } - - ChangeState(State::done, true); + StopProgressEventTimer(); + + MOZ_ASSERT(!mFlagParseBody, + "ChangeStateToDone() called before async HTML parsing is done."); mFlagSend = false; if (mTimeoutTimer) { mTimeoutTimer->Cancel(); } + mLoadTotal = mLoadTransferred; + + // Per spec, fire the last download progress event, if any, + // before readystatechange=4/done. (Note that 0-sized responses + // will have not sent a progress event yet, so one must be sent here). + if (!mFlagSynchronous && + (!mLoadTransferred || mProgressSinceLastProgressEvent)) { + DispatchProgressEvent(this, ProgressEventType::progress, + mLoadTransferred, mLoadTotal); + mProgressSinceLastProgressEvent = false; + } + + // Per spec, fire readystatechange=4/done before final error events. + ChangeState(State::done, true); + + // Per spec, if we failed in the upload phase, fire a final progress, error, + // and loadend event for the upload after readystatechange=4/done. + if (!mFlagSynchronous && mUpload && !mUploadComplete) { + DispatchProgressEvent(mUpload, ProgressEventType::progress, 0, 0); + DispatchProgressEvent(mUpload, ProgressEventType::error, 0, 0); + } + + // Per spec, fire download's load/error and loadend events after + // readystatechange=4/done (and of course all upload events). DispatchProgressEvent(this, - mErrorLoad ? ProgressEventType::error : ProgressEventType::load, - !mErrorLoad, - mLoadTransferred, - mErrorLoad ? 0 : mLoadTransferred); - if (mErrorLoad && mUpload && !mUploadComplete) { - DispatchProgressEvent(mUpload, ProgressEventType::error, true, - mUploadTransferred, mUploadTotal); - } + mErrorLoad ? ProgressEventType::error : + ProgressEventType::load, + mErrorLoad ? 0 : mLoadTransferred, + mErrorLoad ? 0 : mLoadTotal); if (mErrorLoad) { // By nulling out channel here we make it so that Send() can test // for that and throw. Also calling the various status // methods/members will not throw. // This matches what IE does. mChannel = nullptr; } @@ -2504,17 +2571,16 @@ XMLHttpRequestMainThread::SendInternal(c } } mUploadTransferred = 0; mUploadTotal = 0; // By default we don't have any upload, so mark upload complete. mUploadComplete = true; mErrorLoad = false; - mLoadLengthComputable = false; mLoadTotal = 0; if (aBody && httpChannel && !method.LowerCaseEqualsLiteral("get") && !method.LowerCaseEqualsLiteral("head")) { nsAutoCString charset; nsAutoCString defaultContentType; nsCOMPtr<nsIInputStream> postDataStream; @@ -2805,20 +2871,17 @@ XMLHttpRequestMainThread::SendInternal(c suspendedDoc->SuppressEventHandling(nsIDocument::eEvents); } topWindow->SuspendTimeouts(1, false); resumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner); } } } - if (mProgressNotifier) { - mProgressTimerIsActive = false; - mProgressNotifier->Cancel(); - } + StopProgressEventTimer(); { nsAutoSyncOperation sync(suspendedDoc); nsIThread *thread = NS_GetCurrentThread(); while (mFlagSyncLooping) { if (!NS_ProcessNextEvent(thread)) { rv = NS_ERROR_UNEXPECTED; break; @@ -2834,27 +2897,26 @@ XMLHttpRequestMainThread::SendInternal(c if (resumeTimeoutRunnable) { NS_DispatchToCurrentThread(resumeTimeoutRunnable); } } else { // Now that we've successfully opened the channel, we can change state. Note // that this needs to come after the AsyncOpen() and rv check, because this // can run script that would try to restart this request, and that could end // up doing our AsyncOpen on a null channel if the reentered AsyncOpen fails. - if (mProgressNotifier) { - mProgressTimerIsActive = false; - mProgressNotifier->Cancel(); - } - + StopProgressEventTimer(); + + // Upload phase beginning; start the progress event timer if necessary. if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) { StartProgressEventTimer(); } - DispatchProgressEvent(this, ProgressEventType::loadstart, false, 0, 0); + // Dispatch loadstart events + DispatchProgressEvent(this, ProgressEventType::loadstart, 0, 0); if (mUpload && !mUploadComplete) { - DispatchProgressEvent(mUpload, ProgressEventType::loadstart, true, + DispatchProgressEvent(mUpload, ProgressEventType::loadstart, 0, mUploadTotal); } } if (!mChannel) { // Per spec, silently fail on async request failures; throw for sync. if (mFlagSynchronous) { return NS_ERROR_FAILURE; @@ -3116,20 +3178,18 @@ XMLHttpRequestMainThread::SetWithCredent } nsresult XMLHttpRequestMainThread::ChangeState(State aState, bool aBroadcast) { mState = aState; nsresult rv = NS_OK; - if (mProgressNotifier && - aState != State::headers_received && aState != State::loading) { - mProgressTimerIsActive = false; - mProgressNotifier->Cancel(); + if (aState != State::headers_received && aState != State::loading) { + StopProgressEventTimer(); } if (aBroadcast && (!mFlagSynchronous || aState == State::opened || aState == State::done)) { rv = FireReadystatechangeEvent(); } @@ -3220,88 +3280,39 @@ XMLHttpRequestMainThread::OnRedirectVeri return result; } ///////////////////////////////////////////////////// // nsIProgressEventSink methods: // -void -XMLHttpRequestMainThread::MaybeDispatchProgressEvents(bool aFinalProgress) -{ - if (aFinalProgress && mProgressTimerIsActive) { - mProgressTimerIsActive = false; - mProgressNotifier->Cancel(); - } - - if (mProgressTimerIsActive || - !mProgressSinceLastProgressEvent || - mErrorLoad || - mFlagSynchronous) { - return; - } - - if (!aFinalProgress) { - StartProgressEventTimer(); - } - - // We're in the upload phase while our state is State::opened. - if (mState == State::opened) { - if (mUpload && !mUploadComplete) { - DispatchProgressEvent(mUpload, ProgressEventType::progress, - mUploadLengthComputable, mUploadTransferred, - mUploadTotal); - } - } else { - if (aFinalProgress) { - mLoadTotal = mLoadTransferred; - } - mInLoadProgressEvent = true; - DispatchProgressEvent(this, ProgressEventType::progress, - mLoadLengthComputable, mLoadTransferred, - mLoadTotal); - mInLoadProgressEvent = false; - if (mResponseType == XMLHttpRequestResponseType::Moz_chunked_text || - mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer) { - mResponseBody.Truncate(); - mResponseText.Truncate(); - mResultArrayBuffer = nullptr; - mArrayBufferBuilder.reset(); - } - } - - mProgressSinceLastProgressEvent = false; -} - NS_IMETHODIMP XMLHttpRequestMainThread::OnProgress(nsIRequest *aRequest, nsISupports *aContext, int64_t aProgress, int64_t aProgressMax) { - // We're in the upload phase while our state is State::opened. - bool upload = mState == State::opened; // When uploading, OnProgress reports also headers in aProgress and aProgressMax. // So, try to remove the headers, if possible. bool lengthComputable = (aProgressMax != -1); - if (upload) { + if (InUploadPhase()) { int64_t loaded = aProgress; if (lengthComputable) { int64_t headerSize = aProgressMax - mUploadTotal; loaded -= headerSize; } - mUploadLengthComputable = lengthComputable; mUploadTransferred = loaded; mProgressSinceLastProgressEvent = true; - MaybeDispatchProgressEvents((mUploadTransferred == mUploadTotal)); + if (!mFlagSynchronous && !mProgressTimerIsActive) { + StartProgressEventTimer(); + } } else { - mLoadLengthComputable = lengthComputable; mLoadTotal = lengthComputable ? aProgressMax : 0; mLoadTransferred = aProgress; - // Don't dispatch progress events here. OnDataAvailable will take care - // of that. + // OnDataAvailable() handles mProgressSinceLastProgressEvent + // for the download phase. } if (mProgressEventSink) { mProgressEventSink->OnProgress(aRequest, aContext, aProgress, aProgressMax); } return NS_OK; @@ -3485,17 +3496,43 @@ XMLHttpRequestMainThread::Notify(nsITime NS_WARNING("Unexpected timer!"); return NS_ERROR_INVALID_POINTER; } void XMLHttpRequestMainThread::HandleProgressTimerCallback() { mProgressTimerIsActive = false; - MaybeDispatchProgressEvents(false); + + if (!mProgressSinceLastProgressEvent || mErrorLoad) { + return; + } + + if (InUploadPhase()) { + if (mUpload && !mUploadComplete) { + DispatchProgressEvent(mUpload, ProgressEventType::progress, + mUploadTransferred, mUploadTotal); + } + } else { + DispatchProgressEvent(this, ProgressEventType::progress, + mLoadTransferred, mLoadTotal); + } + + mProgressSinceLastProgressEvent = false; + + StartProgressEventTimer(); +} + +void +XMLHttpRequestMainThread::StopProgressEventTimer() +{ + if (mProgressNotifier) { + mProgressTimerIsActive = false; + mProgressNotifier->Cancel(); + } } void XMLHttpRequestMainThread::StartProgressEventTimer() { if (!mProgressNotifier) { mProgressNotifier = do_CreateInstance(NS_TIMER_CONTRACTID); }
--- a/dom/xhr/XMLHttpRequestMainThread.h +++ b/dom/xhr/XMLHttpRequestMainThread.h @@ -467,24 +467,18 @@ public: JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) override; // This fires a trusted readystatechange event, which is not cancelable and // doesn't bubble. nsresult FireReadystatechangeEvent(); void DispatchProgressEvent(DOMEventTargetHelper* aTarget, const ProgressEventType aType, - bool aLengthComputable, int64_t aLoaded, int64_t aTotal); - // Dispatch the "progress" event on the XHR or XHR.upload object if we've - // received data since the last "progress" event. Also dispatches - // "uploadprogress" as needed. - void MaybeDispatchProgressEvents(bool aFinalProgress); - // This is called by the factory constructor. nsresult Init(); nsresult init(nsIPrincipal* principal, nsPIDOMWindowInner* globalObject, nsIURI* baseURI); void SetRequestObserver(nsIRequestObserver* aObserver); @@ -531,20 +525,23 @@ protected: nsresult ChangeState(State aState, bool aBroadcast = true); already_AddRefed<nsILoadGroup> GetLoadGroup() const; nsIURI *GetBaseURI(); already_AddRefed<nsIHttpChannel> GetCurrentHttpChannel(); already_AddRefed<nsIJARChannel> GetCurrentJARChannel(); bool IsSystemXHR() const; + bool InUploadPhase() const; + void OnBodyParseEnd(); void ChangeStateToDone(); void StartProgressEventTimer(); + void StopProgressEventTimer(); nsresult OnRedirectVerifyCallback(nsresult result); nsresult OpenInternal(const nsACString& aMethod, const nsACString& aUrl, const Optional<bool>& aAsync, const Optional<nsAString>& aUsername, const Optional<nsAString>& aPassword); @@ -651,34 +648,32 @@ protected: // finishes downloading (or an error/abort has occurred during either phase). // Used to guard against the user trying to alter headers/etc when it's too // late, and ensure the XHR only handles one in-flight request at once. bool mFlagSend; RefPtr<XMLHttpRequestUpload> mUpload; int64_t mUploadTransferred; int64_t mUploadTotal; - bool mUploadLengthComputable; bool mUploadComplete; bool mProgressSinceLastProgressEvent; // Timeout support PRTime mRequestSentTime; uint32_t mTimeoutMilliseconds; nsCOMPtr<nsITimer> mTimeoutTimer; void StartTimeoutTimer(); void HandleTimeoutCallback(); bool mErrorLoad; bool mWaitingForOnStopRequest; bool mProgressTimerIsActive; bool mIsHtml; bool mWarnAboutMultipartHtml; bool mWarnAboutSyncHtml; - bool mLoadLengthComputable; int64_t mLoadTotal; // 0 if not known. // Amount of script-exposed (i.e. after undoing gzip compresion) data // received. uint64_t mDataAvailable; // Number of HTTP message body bytes received so far. This quantity is // in the same units as Content-Length and mLoadTotal, and hence counts // compressed bytes when the channel has gzip Content-Encoding. If the // channel does not have Content-Encoding, this will be the same as @@ -800,17 +795,17 @@ private: class nsXHRParseEndListener : public nsIDOMEventListener { public: NS_DECL_ISUPPORTS NS_IMETHOD HandleEvent(nsIDOMEvent *event) override { nsCOMPtr<nsIXMLHttpRequest> xhr = do_QueryReferent(mXHR); if (xhr) { - static_cast<XMLHttpRequestMainThread*>(xhr.get())->ChangeStateToDone(); + static_cast<XMLHttpRequestMainThread*>(xhr.get())->OnBodyParseEnd(); } mXHR = nullptr; return NS_OK; } explicit nsXHRParseEndListener(nsIXMLHttpRequest* aXHR) : mXHR(do_GetWeakReference(aXHR)) {} private: virtual ~nsXHRParseEndListener() {}
--- a/dom/xhr/tests/test_xhr_progressevents.html +++ b/dom/xhr/tests/test_xhr_progressevents.html @@ -1,14 +1,14 @@ <!DOCTYPE HTML> <html> <head> <title>Test for XMLHttpRequest Progress Events</title> <script type="text/javascript" src="/MochiKit/packed.js"></script> - <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body onload="gen.next();"> <pre id=l></pre> <script type="application/javascript;version=1.7"> SimpleTest.waitForExplicitFinish(); var gen = runTests(); @@ -28,17 +28,17 @@ function getEvent(e) { function startsWith(a, b) { return a.substr(0, b.length) === b; } function updateProgress(e, data, testName) { var test = " while running " + testName; is(e.type, "progress", "event type" + test); - + let response; if (data.nodata) { is(e.target.response, null, "response should be null" + test); response = null; } else if (data.text) { is(typeof e.target.response, "string", "response should be a string" + test); response = e.target.response; @@ -69,17 +69,17 @@ function updateProgress(e, data, testNam ok(e.loaded - data.receivedBytes <= data.pendingBytes, "event.loaded didn't increase too much" + test); if (!data.nodata && !data.blob) { var newData; ok(startsWith(response, data.receivedResult), "response strictly grew" + test); newData = response.substr(data.receivedResult.length); - + if (!data.encoded) { ok(newData.length > 0, "sanity check for progress" + test); } ok(startsWith(data.pendingResult, newData), "new data matches expected" + test); } is(e.lengthComputable, "total" in data, "lengthComputable" + test); if ("total" in data) { @@ -194,30 +194,30 @@ function runTests() { receivedBytes: 0, total: test.total, encoded: test.encoded, nodata: responseType.nodata, chunked: responseType.chunked, text: responseType.text, blob: responseType.blob, file: test.file }; - + xhr.onreadystatechange = null; if (testState.file) xhr.open("GET", test.file); else xhr.open("POST", "progressserver.sjs?open&" + test.open); xhr.responseType = responseType.type; xhr.send("ready"); xhr.onreadystatechange = getEvent; let e = yield undefined; is(e.type, "readystatechange", "should readystate to headers-received starting " + testState.name); is(xhr.readyState, xhr.HEADERS_RECEIVED, "should be in state HEADERS_RECEIVED starting " + testState.name); - + e = yield undefined; is(e.type, "readystatechange", "should readystate to loading starting " + testState.name); is(xhr.readyState, xhr.LOADING, "should be in state LOADING starting " + testState.name); if (typeof testState.total == "undefined") delete testState.total; } if ("file" in test) { testState.pendingBytes = testState.total; @@ -233,29 +233,29 @@ function runTests() { is(e.type, "readystatechange", "should readystate to done closing " + testState.name); is(xhr.readyState, xhr.DONE, "should be in state DONE closing " + testState.name); log("readystate to 4"); if (responseType.chunked) { xhr.responseType; is(xhr.response, null, "chunked data has null response for " + testState.name); } - + e = yield undefined; is(e.type, "load", "should fire load closing " + testState.name); - is(e.lengthComputable, true, "length should be computable during load closing " + testState.name); + is(e.lengthComputable, e.total != 0, "length should " + (e.total == 0 ? "not " : "") + "be computable during load closing " + testState.name); log("got load"); if (responseType.chunked) { is(xhr.response, null, "chunked data has null response for " + testState.name); } - + e = yield undefined; is(e.type, "loadend", "should fire loadend closing " + testState.name); - is(e.lengthComputable, true, "length should be computable during loadend closing " + testState.name); + is(e.lengthComputable, e.total != 0, "length should " + (e.total == 0 ? "not " : "") + "be computable during loadend closing " + testState.name); log("got loadend"); // if we closed the connection using an explicit request, make sure that goes through before // running the next test in order to avoid reordered requests from closing the wrong // connection. if (xhrClose && xhrClose.readyState != xhrClose.DONE) { log("wait for closeConn to finish"); xhrClose.onloadend = getEvent; @@ -293,38 +293,38 @@ function runTests() { testState.pendingResult += "utf16" in test ? test.utf16 : test.data; } else { testState.pendingResult += test.data; } testState.pendingBytes = test.data.length; sendData(test.data); } - + while(testState.pendingBytes) { log("waiting for more bytes: " + testState.pendingBytes); e = yield undefined; // Readystate can fire several times between each progress event. if (e.type === "readystatechange") continue; - + updateProgress(e, testState, "data for " + testState.name + "[" + testState.index + "]"); if (responseType.chunked) { testState.receivedResult = ""; } } if (!testState.nodata && !testState.blob) { is(testState.pendingResult, "", "should have consumed the expected result"); } log("done with this test"); } - + is(testState.name, "", "forgot to close last test"); } SimpleTest.finish(); yield undefined; } </script>
--- a/dom/xul/nsXULElement.cpp +++ b/dom/xul/nsXULElement.cpp @@ -1692,51 +1692,24 @@ nsXULElement::GetParentTree(nsIDOMXULMul NS_IMETHODIMP nsXULElement::Focus() { ErrorResult rv; Focus(rv); return rv.StealNSResult(); } -void -nsXULElement::Focus(ErrorResult& rv) -{ - nsIFocusManager* fm = nsFocusManager::GetFocusManager(); - nsCOMPtr<nsIDOMElement> elem = do_QueryObject(this); - if (fm) { - rv = fm->SetFocus(this, 0); - } -} - NS_IMETHODIMP nsXULElement::Blur() { ErrorResult rv; Blur(rv); return rv.StealNSResult(); } -void -nsXULElement::Blur(ErrorResult& rv) -{ - if (!ShouldBlur(this)) - return; - - nsIDocument* doc = GetComposedDoc(); - if (!doc) - return; - - nsPIDOMWindowOuter* win = doc->GetWindow(); - nsIFocusManager* fm = nsFocusManager::GetFocusManager(); - if (win && fm) { - rv = fm->ClearFocus(win); - } -} - NS_IMETHODIMP nsXULElement::Click() { return ClickWithInputSource(nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN, /* aIsTrusted = */ true); } void nsXULElement::Click(ErrorResult& rv)
--- a/dom/xul/nsXULElement.h +++ b/dom/xul/nsXULElement.h @@ -336,16 +336,18 @@ enum { ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 3); #undef XUL_ELEMENT_FLAG_BIT class nsXULElement final : public nsStyledElement, public nsIDOMXULElement { public: + using Element::Blur; + using Element::Focus; explicit nsXULElement(already_AddRefed<mozilla::dom::NodeInfo> aNodeInfo); static nsresult Create(nsXULPrototypeElement* aPrototype, nsIDocument* aDocument, bool aIsScriptable, bool aIsRoot, mozilla::dom::Element** aResult); NS_IMPL_FROMCONTENT(nsXULElement, kNameSpaceID_XUL) @@ -556,18 +558,16 @@ public: { return BoolAttrIsTrue(nsGkAtoms::allowevents); } already_AddRefed<nsIRDFCompositeDataSource> GetDatabase(); already_AddRefed<nsIXULTemplateBuilder> GetBuilder(); already_AddRefed<nsIRDFResource> GetResource(mozilla::ErrorResult& rv); nsIControllers* GetControllers(mozilla::ErrorResult& rv); already_AddRefed<mozilla::dom::BoxObject> GetBoxObject(mozilla::ErrorResult& rv); - void Focus(mozilla::ErrorResult& rv); - void Blur(mozilla::ErrorResult& rv); void Click(mozilla::ErrorResult& rv); // The XPCOM DoCommand never fails, so it's OK for us. already_AddRefed<nsINodeList> GetElementsByAttribute(const nsAString& aAttribute, const nsAString& aValue); already_AddRefed<nsINodeList> GetElementsByAttributeNS(const nsAString& aNamespaceURI, const nsAString& aAttribute,
--- a/gfx/2d/2D.h +++ b/gfx/2d/2D.h @@ -1032,16 +1032,19 @@ public: SurfaceFormat aFormat) const = 0; /** * Create a SourceSurface optimized for use with this DrawTarget from an * arbitrary SourceSurface type supported by this backend. This may return * aSourceSurface or some other existing surface. */ virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const = 0; + virtual already_AddRefed<SourceSurface> OptimizeSourceSurfaceForUnknownAlpha(SourceSurface *aSurface) const { + return OptimizeSourceSurface(aSurface); + } /** * Create a SourceSurface for a type of NativeSurface. This may fail if the * draw target does not know how to deal with the type of NativeSurface passed * in. If this succeeds, the SourceSurface takes the ownersip of the NativeSurface. */ virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const = 0;
--- a/gfx/2d/DrawTargetSkia.cpp +++ b/gfx/2d/DrawTargetSkia.cpp @@ -98,16 +98,41 @@ static void ReleaseTemporarySurface(void* aPixels, void* aContext) { DataSourceSurface* surf = static_cast<DataSourceSurface*>(aContext); if (surf) { surf->Release(); } } +static void +WriteRGBXFormat(uint8_t* aData, const IntSize &aSize, + const int32_t aStride, SurfaceFormat aFormat) +{ + if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) { + return; + } + + int height = aSize.height; + int width = aSize.width * 4; + + for (int row = 0; row < height; ++row) { + for (int column = 0; column < width; column += 4) { +#ifdef IS_BIG_ENDIAN + aData[column] = 0xFF; +#else + aData[column + 3] = 0xFF; +#endif + } + aData += aStride; + } + + return; +} + #ifdef DEBUG static bool VerifyRGBXFormat(uint8_t* aData, const IntSize &aSize, const int32_t aStride, SurfaceFormat aFormat) { if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) { return true; } // We should've initialized the data to be opaque already @@ -1372,53 +1397,86 @@ DrawTargetSkia::UsingSkiaGPU() const { #ifdef USE_SKIA_GPU return !!mGrContext; #else return false; #endif } +#ifdef USE_SKIA_GPU +already_AddRefed<SourceSurface> +DrawTargetSkia::OptimizeGPUSourceSurface(SourceSurface *aSurface) const +{ + // Check if the underlying SkBitmap already has an associated GrTexture. + if (aSurface->GetType() == SurfaceType::SKIA && + static_cast<SourceSurfaceSkia*>(aSurface)->GetBitmap().getTexture()) { + RefPtr<SourceSurface> surface(aSurface); + return surface.forget(); + } + + SkBitmap bitmap = GetBitmapForSurface(aSurface); + + // Upload the SkBitmap to a GrTexture otherwise. + SkAutoTUnref<GrTexture> texture( + GrRefCachedBitmapTexture(mGrContext.get(), bitmap, GrTextureParams::ClampBilerp())); + + if (texture) { + // Create a new SourceSurfaceSkia whose SkBitmap contains the GrTexture. + RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia(); + if (surface->InitFromGrTexture(texture, aSurface->GetSize(), aSurface->GetFormat())) { + return surface.forget(); + } + } + + // The data was too big to fit in a GrTexture. + if (aSurface->GetType() == SurfaceType::SKIA) { + // It is already a Skia source surface, so just reuse it as-is. + RefPtr<SourceSurface> surface(aSurface); + return surface.forget(); + } + + // Wrap it in a Skia source surface so that can do tiled uploads on-demand. + RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia(); + surface->InitFromBitmap(bitmap); + return surface.forget(); +} +#endif + +already_AddRefed<SourceSurface> +DrawTargetSkia::OptimizeSourceSurfaceForUnknownAlpha(SourceSurface *aSurface) const +{ +#ifdef USE_SKIA_GPU + if (UsingSkiaGPU()) { + return OptimizeGPUSourceSurface(aSurface); + } +#endif + + if (aSurface->GetType() == SurfaceType::SKIA) { + RefPtr<SourceSurface> surface(aSurface); + return surface.forget(); + } + + RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface(); + + // For plugins, GDI can sometimes just write 0 to the alpha channel + // even for RGBX formats. In this case, we have to manually write + // the alpha channel to make Skia happy with RGBX and in case GDI + // writes some bad data. Luckily, this only happens on plugins. + WriteRGBXFormat(dataSurface->GetData(), dataSurface->GetSize(), + dataSurface->Stride(), dataSurface->GetFormat()); + return dataSurface.forget(); +} + already_AddRefed<SourceSurface> DrawTargetSkia::OptimizeSourceSurface(SourceSurface *aSurface) const { #ifdef USE_SKIA_GPU if (UsingSkiaGPU()) { - // Check if the underlying SkBitmap already has an associated GrTexture. - if (aSurface->GetType() == SurfaceType::SKIA && - static_cast<SourceSurfaceSkia*>(aSurface)->GetBitmap().getTexture()) { - RefPtr<SourceSurface> surface(aSurface); - return surface.forget(); - } - - SkBitmap bitmap = GetBitmapForSurface(aSurface); - - // Upload the SkBitmap to a GrTexture otherwise. - SkAutoTUnref<GrTexture> texture( - GrRefCachedBitmapTexture(mGrContext.get(), bitmap, GrTextureParams::ClampBilerp())); - - if (texture) { - // Create a new SourceSurfaceSkia whose SkBitmap contains the GrTexture. - RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia(); - if (surface->InitFromGrTexture(texture, aSurface->GetSize(), aSurface->GetFormat())) { - return surface.forget(); - } - } - - // The data was too big to fit in a GrTexture. - if (aSurface->GetType() == SurfaceType::SKIA) { - // It is already a Skia source surface, so just reuse it as-is. - RefPtr<SourceSurface> surface(aSurface); - return surface.forget(); - } - - // Wrap it in a Skia source surface so that can do tiled uploads on-demand. - RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia(); - surface->InitFromBitmap(bitmap); - return surface.forget(); + return OptimizeGPUSourceSurface(aSurface); } #endif if (aSurface->GetType() == SurfaceType::SKIA) { RefPtr<SourceSurface> surface(aSurface); return surface.forget(); }
--- a/gfx/2d/DrawTargetSkia.h +++ b/gfx/2d/DrawTargetSkia.h @@ -111,16 +111,17 @@ public: const IntRect& aBounds = IntRect(), bool aCopyBackground = false) override; virtual void PopLayer() override; virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat) const override; virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override; + virtual already_AddRefed<SourceSurface> OptimizeSourceSurfaceForUnknownAlpha(SourceSurface *aSurface) const override; virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override; virtual already_AddRefed<DrawTarget> CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override; virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override; virtual already_AddRefed<GradientStops> CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, ExtendMode aExtendMode = ExtendMode::CLAMP) const override; virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override; virtual void SetTransform(const Matrix &aTransform) override; @@ -136,16 +137,18 @@ public: SurfaceFormat aFormat, bool aCached); virtual bool InitWithGrContext(GrContext* aGrContext, const IntSize &aSize, SurfaceFormat aFormat) override { return InitWithGrContext(aGrContext, aSize, aFormat, false); } + + already_AddRefed<SourceSurface> OptimizeGPUSourceSurface(SourceSurface *aSurface) const; #endif // Skia assumes that texture sizes fit in 16-bit signed integers. static size_t GetMaxSurfaceSize() { return 32767; } operator std::string() const {
--- a/gfx/thebes/DeviceManagerD3D11.cpp +++ b/gfx/thebes/DeviceManagerD3D11.cpp @@ -50,17 +50,16 @@ DeviceManagerD3D11::DeviceManagerD3D11() { // Set up the D3D11 feature levels we can ask for. if (IsWin8OrLater()) { mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_1); } mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_0); mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_1); mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_0); - mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_9_3); } static inline bool IsWARPStable() { // It seems like nvdxgiwrap makes a mess of WARP. See bug 1154703. if (!IsWin8OrLater() || GetModuleHandleA("nvdxgiwrap.dll")) { return false;
--- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -1001,17 +1001,19 @@ void SourceSurfaceDestroyed(void *aData) void gfxPlatform::ClearSourceSurfaceForSurface(gfxASurface *aSurface) { aSurface->SetData(&kSourceSurface, nullptr, nullptr); } /* static */ already_AddRefed<SourceSurface> -gfxPlatform::GetSourceSurfaceForSurface(DrawTarget *aTarget, gfxASurface *aSurface) +gfxPlatform::GetSourceSurfaceForSurface(DrawTarget *aTarget, + gfxASurface *aSurface, + bool aIsPlugin) { if (!aSurface->CairoSurface() || aSurface->CairoStatus()) { return nullptr; } if (!aTarget) { aTarget = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); } @@ -1059,17 +1061,19 @@ gfxPlatform::GetSourceSurfaceForSurface( // Currently no other DrawTarget types implement CreateSourceSurfaceFromNativeSurface if (!srcBuffer) { // If aSurface wraps data, we can create a SourceSurfaceRawData that wraps // the same data, then optimize it for aTarget: RefPtr<DataSourceSurface> surf = GetWrappedDataSourceSurface(aSurface); if (surf) { - srcBuffer = aTarget->OptimizeSourceSurface(surf); + srcBuffer = aIsPlugin ? aTarget->OptimizeSourceSurfaceForUnknownAlpha(surf) + : aTarget->OptimizeSourceSurface(surf); + if (srcBuffer == surf) { // GetWrappedDataSourceSurface returns a SourceSurface that holds a // strong reference to aSurface since it wraps aSurface's data and // needs it to stay alive. As a result we can't cache srcBuffer on // aSurface (below) since aSurface would then hold a strong reference // back to srcBuffer, creating a reference loop and a memory leak. Not // caching is fine since wrapping is cheap enough (no copying) so we // can just wrap again next time we're called.
--- a/gfx/thebes/gfxPlatform.h +++ b/gfx/thebes/gfxPlatform.h @@ -205,19 +205,24 @@ public: * The returned surface keeps a reference to aTarget, so it is OK to keep the * surface, even if aTarget changes. * aTarget should not keep a reference to the returned surface because that * will cause a cycle. * * This function is static so that it can be accessed from * PluginInstanceChild (where we can't call gfxPlatform::GetPlatform() * because the prefs service can only be accessed from the main process). + * + * aIsPlugin is used to tell the backend that they can optimize this surface + * specifically because it's used for a plugin. This is mostly for Skia. */ static already_AddRefed<SourceSurface> - GetSourceSurfaceForSurface(mozilla::gfx::DrawTarget *aTarget, gfxASurface *aSurface); + GetSourceSurfaceForSurface(mozilla::gfx::DrawTarget *aTarget, + gfxASurface *aSurface, + bool aIsPlugin = false); static void ClearSourceSurfaceForSurface(gfxASurface *aSurface); static already_AddRefed<DataSourceSurface> GetWrappedDataSourceSurface(gfxASurface *aSurface); virtual already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont);
--- a/image/ImageCacheKey.cpp +++ b/image/ImageCacheKey.cpp @@ -40,67 +40,79 @@ BlobSerial(ImageURL* aURI) if (NS_SUCCEEDED(NS_GetBlobForBlobURISpec(spec, getter_AddRefs(blob))) && blob) { return Some(blob->GetSerialNumber()); } return Nothing(); } -ImageCacheKey::ImageCacheKey(nsIURI* aURI, nsIDocument* aDocument) +ImageCacheKey::ImageCacheKey(nsIURI* aURI, + const PrincipalOriginAttributes& aAttrs, + nsIDocument* aDocument) : mURI(new ImageURL(aURI)) + , mOriginAttributes(aAttrs) , mControlledDocument(GetControlledDocumentToken(aDocument)) , mIsChrome(URISchemeIs(mURI, "chrome")) { MOZ_ASSERT(NS_IsMainThread()); if (URISchemeIs(mURI, "blob")) { mBlobSerial = BlobSerial(mURI); } - mHash = ComputeHash(mURI, mBlobSerial, mControlledDocument); + mHash = ComputeHash(mURI, mBlobSerial, mOriginAttributes, mControlledDocument); } -ImageCacheKey::ImageCacheKey(ImageURL* aURI, nsIDocument* aDocument) +ImageCacheKey::ImageCacheKey(ImageURL* aURI, + const PrincipalOriginAttributes& aAttrs, + nsIDocument* aDocument) : mURI(aURI) + , mOriginAttributes(aAttrs) , mControlledDocument(GetControlledDocumentToken(aDocument)) , mIsChrome(URISchemeIs(mURI, "chrome")) { MOZ_ASSERT(aURI); if (URISchemeIs(mURI, "blob")) { mBlobSerial = BlobSerial(mURI); } - mHash = ComputeHash(mURI, mBlobSerial, mControlledDocument); + mHash = ComputeHash(mURI, mBlobSerial, mOriginAttributes, mControlledDocument); } ImageCacheKey::ImageCacheKey(const ImageCacheKey& aOther) : mURI(aOther.mURI) , mBlobSerial(aOther.mBlobSerial) + , mOriginAttributes(aOther.mOriginAttributes) , mControlledDocument(aOther.mControlledDocument) , mHash(aOther.mHash) , mIsChrome(aOther.mIsChrome) { } ImageCacheKey::ImageCacheKey(ImageCacheKey&& aOther) : mURI(Move(aOther.mURI)) , mBlobSerial(Move(aOther.mBlobSerial)) + , mOriginAttributes(aOther.mOriginAttributes) , mControlledDocument(aOther.mControlledDocument) , mHash(aOther.mHash) , mIsChrome(aOther.mIsChrome) { } bool ImageCacheKey::operator==(const ImageCacheKey& aOther) const { // Don't share the image cache between a controlled document and anything else. if (mControlledDocument != aOther.mControlledDocument) { return false; } + // The origin attributes always have to match. + if (mOriginAttributes != aOther.mOriginAttributes) { + return false; + } if (mBlobSerial || aOther.mBlobSerial) { // If at least one of us has a blob serial, just compare the blob serial and // the ref portion of the URIs. return mBlobSerial == aOther.mBlobSerial && mURI->HasSameRef(*aOther.mURI); } // For non-blob URIs, compare the URIs. @@ -111,37 +123,41 @@ const char* ImageCacheKey::Spec() const { return mURI->Spec(); } /* static */ uint32_t ImageCacheKey::ComputeHash(ImageURL* aURI, const Maybe<uint64_t>& aBlobSerial, + const PrincipalOriginAttributes& aAttrs, void* aControlledDocument) { // Since we frequently call Hash() several times in a row on the same // ImageCacheKey, as an optimization we compute our hash once and store it. nsPrintfCString ptr("%p", aControlledDocument); + nsAutoCString suffix; + aAttrs.CreateSuffix(suffix); + if (aBlobSerial) { // For blob URIs, we hash the serial number of the underlying blob, so that // different blob URIs which point to the same blob share a cache entry. We // also include the ref portion of the URI to support -moz-samplesize, which // requires us to create different Image objects even if the source data is // the same. nsAutoCString ref; aURI->GetRef(ref); - return HashGeneric(*aBlobSerial, HashString(ref + ptr)); + return HashGeneric(*aBlobSerial, HashString(ref + suffix + ptr)); } // For non-blob URIs, we hash the URI spec. nsAutoCString spec; aURI->GetSpec(spec); - return HashString(spec + ptr); + return HashString(spec + suffix + ptr); } /* static */ void* ImageCacheKey::GetControlledDocumentToken(nsIDocument* aDocument) { // For non-controlled documents, we just return null. For controlled // documents, we cast the pointer into a void* to avoid dereferencing // it (since we only use it for comparisons), and return it.
--- a/image/ImageCacheKey.h +++ b/image/ImageCacheKey.h @@ -5,16 +5,17 @@ /** * ImageCacheKey is the key type for the image cache (see imgLoader.h). */ #ifndef mozilla_image_src_ImageCacheKey_h #define mozilla_image_src_ImageCacheKey_h +#include "mozilla/BasePrincipal.h" #include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" class nsIDocument; class nsIURI; namespace mozilla { namespace image { @@ -27,18 +28,20 @@ class ImageURL; * We key the cache on the initial URI (before any redirects), with some * canonicalization applied. See ComputeHash() for the details. * Controlled documents do not share their cache entries with * non-controlled documents, or other controlled documents. */ class ImageCacheKey final { public: - ImageCacheKey(nsIURI* aURI, nsIDocument* aDocument); - ImageCacheKey(ImageURL* aURI, nsIDocument* aDocument); + ImageCacheKey(nsIURI* aURI, const PrincipalOriginAttributes& aAttrs, + nsIDocument* aDocument); + ImageCacheKey(ImageURL* aURI, const PrincipalOriginAttributes& aAttrs, + nsIDocument* aDocument); ImageCacheKey(const ImageCacheKey& aOther); ImageCacheKey(ImageCacheKey&& aOther); bool operator==(const ImageCacheKey& aOther) const; uint32_t Hash() const { return mHash; } /// A weak pointer to the URI spec for this cache entry. For logging only. @@ -49,21 +52,23 @@ public: /// A token indicating which service worker controlled document this entry /// belongs to, if any. void* ControlledDocument() const { return mControlledDocument; } private: static uint32_t ComputeHash(ImageURL* aURI, const Maybe<uint64_t>& aBlobSerial, + const PrincipalOriginAttributes& aAttrs, void* aControlledDocument); static void* GetControlledDocumentToken(nsIDocument* aDocument); RefPtr<ImageURL> mURI; Maybe<uint64_t> mBlobSerial; + PrincipalOriginAttributes mOriginAttributes; void* mControlledDocument; uint32_t mHash; bool mIsChrome; }; } // namespace image } // namespace mozilla
--- a/image/imgFrame.cpp +++ b/image/imgFrame.cpp @@ -17,17 +17,16 @@ #include "gfxAlphaRecovery.h" #include "GeckoProfiler.h" #include "MainThreadUtils.h" #include "mozilla/CheckedInt.h" #include "mozilla/gfx/Tools.h" #include "mozilla/Likely.h" #include "mozilla/MemoryReporting.h" -#include "mozilla/Telemetry.h" #include "nsMargin.h" #include "nsThreadUtils.h" namespace mozilla { using namespace gfx; @@ -429,69 +428,21 @@ imgFrame::Optimize() } // XXX(seth): It's currently unclear if there's any reason why we can't // optimize non-premult surfaces. We should look into removing this. if (mNonPremult) { return NS_OK; } -#ifdef ANDROID - SurfaceFormat optFormat = gfxPlatform::GetPlatform() - ->Optimal2DFormatForContent(gfxContentType::COLOR); - - if (mFormat != SurfaceFormat::B8G8R8A8 && - optFormat == SurfaceFormat::R5G6B5_UINT16) { - Telemetry::Accumulate(Telemetry::IMAGE_OPTIMIZE_TO_565_USED, true); - - RefPtr<VolatileBuffer> buf = - AllocateBufferForImage(mFrameRect.Size(), optFormat); - if (!buf) { - return NS_OK; - } - - RefPtr<DataSourceSurface> surf = - CreateLockedSurface(buf, mFrameRect.Size(), optFormat); - if (!surf) { - return NS_ERROR_OUT_OF_MEMORY; - } - - DataSourceSurface::MappedSurface mapping; - if (!surf->Map(DataSourceSurface::MapType::WRITE, &mapping)) { - gfxCriticalError() << "imgFrame::Optimize failed to map surface"; - return NS_ERROR_FAILURE; - } - - RefPtr<DrawTarget> target = - Factory::CreateDrawTargetForData(BackendType::CAIRO, - mapping.mData, - mFrameRect.Size(), - mapping.mStride, - optFormat); - - if (!target) { - gfxWarning() << "imgFrame::Optimize failed in CreateDrawTargetForData"; - return NS_ERROR_OUT_OF_MEMORY; - } - Rect rect(0, 0, mFrameRect.width, mFrameRect.height); - target->DrawSurface(mImageSurface, rect, rect); - target->Flush(); - surf->Unmap(); - - mImageSurface = surf; - mVBuf = buf; - mFormat = optFormat; - } -#else mOptSurface = gfxPlatform::GetPlatform() ->ScreenReferenceDrawTarget()->OptimizeSourceSurface(mImageSurface); if (mOptSurface == mImageSurface) { mOptSurface = nullptr; } -#endif if (mOptSurface) { mVBuf = nullptr; mVBufPtr = nullptr; mImageSurface = nullptr; } #ifdef MOZ_WIDGET_ANDROID @@ -830,21 +781,23 @@ imgFrame::UnlockImageData() if (mLockCount == 1 && !mPalettedImageData) { // We can't safely optimize off-main-thread, so create a runnable to do it. if (!NS_IsMainThread()) { nsCOMPtr<nsIRunnable> runnable = new UnlockImageDataRunnable(this); NS_DispatchToMainThread(runnable); return NS_OK; } - // Convert the data surface to a GPU surface or a single color if possible. - // This will also release mImageSurface if possible. + // Convert our data surface to a GPU surface if possible. We'll also try to + // release mImageSurface. Optimize(); - // Allow the OS to release our data surface. + // Allow the OS to release our data surface. Note that mImageSurface also + // keeps our volatile buffer alive, so this doesn't actually work unless we + // released mImageSurface in Optimize(). mVBufPtr = nullptr; } mLockCount--; return NS_OK; }
--- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -1347,17 +1347,26 @@ imgLoader::ClearCache(bool chrome) NS_IMETHODIMP imgLoader::FindEntryProperties(nsIURI* uri, nsIDOMDocument* aDOMDoc, nsIProperties** _retval) { *_retval = nullptr; nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDOMDoc); - ImageCacheKey key(uri, doc); + + PrincipalOriginAttributes attrs; + if (doc) { + nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal(); + if (principal) { + attrs = BasePrincipal::Cast(principal)->OriginAttributesRef(); + } + } + + ImageCacheKey key(uri, attrs, doc); imgCacheTable& cache = GetCache(key); RefPtr<imgCacheEntry> entry; if (cache.Get(key, getter_AddRefs(entry)) && entry) { if (mCacheTracker && entry->HasNoProxies()) { mCacheTracker->MarkUsed(entry); } @@ -2105,17 +2114,21 @@ imgLoader::LoadImage(nsIURI* aURI, } RefPtr<imgCacheEntry> entry; // Look in the cache for our URI, and then validate it. // XXX For now ignore aCacheKey. We will need it in the future // for correctly dealing with image load requests that are a result // of post data. - ImageCacheKey key(aURI, aLoadingDocument); + PrincipalOriginAttributes attrs; + if (aLoadingPrincipal) { + attrs = BasePrincipal::Cast(aLoadingPrincipal)->OriginAttributesRef(); + } + ImageCacheKey key(aURI, attrs, aLoadingDocument); imgCacheTable& cache = GetCache(key); if (cache.Get(key, getter_AddRefs(entry)) && entry) { if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerURI, aReferrerPolicy, aLoadGroup, aObserver, aLoadingDocument, requestFlags, aContentPolicyType, true, _retval, aLoadingPrincipal, corsmode)) { request = entry->GetRequest(); @@ -2309,17 +2322,26 @@ imgLoader::LoadImageWithChannel(nsIChann MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy); RefPtr<imgRequest> request; nsCOMPtr<nsIURI> uri; channel->GetURI(getter_AddRefs(uri)); nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX); - ImageCacheKey key(uri, doc); + + NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); + nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo(); + + PrincipalOriginAttributes attrs; + if (loadInfo) { + attrs.InheritFromNecko(loadInfo->GetOriginAttributes()); + } + + ImageCacheKey key(uri, attrs, doc); nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; channel->GetLoadFlags(&requestFlags); RefPtr<imgCacheEntry> entry; if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) { RemoveFromCache(key); @@ -2411,17 +2433,17 @@ imgLoader::LoadImageWithChannel(nsIChann // We use originalURI here to fulfil the imgIRequest contract on GetURI. nsCOMPtr<nsIURI> originalURI; channel->GetOriginalURI(getter_AddRefs(originalURI)); // XXX(seth): We should be able to just use |key| here, except that |key| is // constructed above with the *current URI* and not the *original URI*. I'm // pretty sure this is a bug, and it's preventing us from ever getting a // cache hit in LoadImageWithChannel when redirects are involved. - ImageCacheKey originalURIKey(originalURI, doc); + ImageCacheKey originalURIKey(originalURI, attrs, doc); // Default to doing a principal check because we don't know who // started that load and whether their principal ended up being // inherited on the channel. NewRequestAndEntry(/* aForcePrincipalCheckForCacheEntry = */ true, this, originalURIKey, getter_AddRefs(request), getter_AddRefs(entry));
--- a/ipc/glue/IPCMessageUtils.cpp +++ b/ipc/glue/IPCMessageUtils.cpp @@ -7,17 +7,17 @@ #include "IPCMessageUtils.h" #include "mozilla/CheckedInt.h" namespace IPC { bool ByteLengthIsValid(uint32_t aNumElements, size_t aElementSize, int* aByteLength) { - auto length = CheckedInt<int>(aNumElements) * aElementSize; + auto length = mozilla::CheckedInt<int>(aNumElements) * aElementSize; if (!length.isValid()) { return false; } *aByteLength = length.value(); return true; } } // namespace IPC
--- a/ipc/glue/IPCStreamUtils.cpp +++ b/ipc/glue/IPCStreamUtils.cpp @@ -5,21 +5,25 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "IPCStreamUtils.h" #include "nsIIPCSerializableInputStream.h" #include "mozilla/Assertions.h" #include "mozilla/dom/PContentChild.h" +#include "mozilla/dom/PContentParent.h" #include "mozilla/dom/File.h" #include "mozilla/ipc/FileDescriptorSetChild.h" #include "mozilla/ipc/FileDescriptorSetParent.h" +#include "mozilla/ipc/InputStreamUtils.h" #include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ipc/PBackgroundParent.h" #include "mozilla/ipc/SendStream.h" +#include "mozilla/unused.h" #include "nsIAsyncInputStream.h" namespace mozilla { namespace ipc { namespace { // These serialization and cleanup functions could be externally exposed. For @@ -340,17 +344,17 @@ AutoIPCStream::~AutoIPCStream() if (mValue && IsSet()) { CleanupIPCStream(*mValue, mTaken); } else { CleanupIPCStream(*mOptionalValue, mTaken); } } void -AutoIPCStream::Serialize(nsIInputStream* aStream, PContentChild* aManager) +AutoIPCStream::Serialize(nsIInputStream* aStream, dom::PContentChild* aManager) { MOZ_ASSERT(aStream); MOZ_ASSERT(aManager); MOZ_ASSERT(mValue || mOptionalValue); MOZ_ASSERT(!mTaken); MOZ_ASSERT(!IsSet()); if (mValue) {
--- a/ipc/glue/MessageChannel.cpp +++ b/ipc/glue/MessageChannel.cpp @@ -26,17 +26,17 @@ using mozilla::Move; // Undo the damage done by mozzconf.h #undef compress // Logging seems to be somewhat broken on b2g. #ifdef MOZ_B2G #define IPC_LOG(...) #else -static LazyLogModule sLogModule("ipc"); +static mozilla::LazyLogModule sLogModule("ipc"); #define IPC_LOG(...) MOZ_LOG(sLogModule, LogLevel::Debug, (__VA_ARGS__)) #endif /* * IPC design: * * There are three kinds of messages: async, sync, and intr. Sync and intr * messages are blocking. Only intr and high-priority sync messages can nest. @@ -92,16 +92,17 @@ static LazyLogModule sLogModule("ipc"); * message, we may dispatch an async message. This causes some additional * complexity. One issue is that replies can be received out of order. It's also * more difficult to determine whether one message is nested inside * another. Consequently, intr handling uses mOutOfTurnReplies and * mRemoteStackDepthGuess, which are not needed for sync messages. */ using namespace mozilla; +using namespace mozilla::ipc; using namespace std; using mozilla::dom::AutoNoJSAPI; using mozilla::dom::ScriptSettingsInitialized; using mozilla::MonitorAutoLock; using mozilla::MonitorAutoUnlock; #define IPC_ASSERT(_cond, ...) \
--- a/ipc/glue/ProtocolUtils.cpp +++ b/ipc/glue/ProtocolUtils.cpp @@ -5,20 +5,23 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "base/process_util.h" #ifdef OS_POSIX #include <errno.h> #endif +#include "mozilla/ipc/ProtocolUtils.h" + +#include "mozilla/dom/ContentParent.h" #include "mozilla/ipc/MessageChannel.h" -#include "mozilla/ipc/ProtocolUtils.h" #include "mozilla/ipc/Transport.h" #include "mozilla/StaticMutex.h" +#include "nsPrintfCString.h" #if defined(MOZ_SANDBOX) && defined(XP_WIN) #define TARGET_SANDBOX_EXPORTS #include "mozilla/sandboxTarget.h" #endif #if defined(MOZ_CRASHREPORTER) && defined(XP_WIN) #include "aclapi.h"
--- a/js/src/asmjs/WasmInstance.cpp +++ b/js/src/asmjs/WasmInstance.cpp @@ -396,17 +396,17 @@ Instance::~Instance() void Instance::tracePrivate(JSTracer* trc) { // This method is only called from WasmInstanceObject so the only reason why // TraceEdge is called is so that the pointer can be updated during a moving // GC. TraceWeakEdge may sound better, but it is less efficient given that // we know object_ is already marked. - MOZ_ASSERT(!IsAboutToBeFinalized(&object_)); + MOZ_ASSERT(!gc::IsAboutToBeFinalized(&object_)); TraceEdge(trc, &object_, "wasm instance object"); for (const FuncImport& fi : metadata().funcImports) TraceNullableEdge(trc, &funcImportTls(fi).obj, "wasm import"); for (const SharedTable& table : tables_) table->trace(trc);
--- a/js/src/asmjs/WasmJS.cpp +++ b/js/src/asmjs/WasmJS.cpp @@ -851,17 +851,17 @@ WasmTableObject::finalize(FreeOp* fop, J tableObj.table().Release(); } /* static */ void WasmTableObject::trace(JSTracer* trc, JSObject* obj) { WasmTableObject& tableObj = obj->as<WasmTableObject>(); if (!tableObj.isNewborn()) - tableObj.table().trace(trc); + tableObj.table().tracePrivate(trc); } /* static */ WasmTableObject* WasmTableObject::create(JSContext* cx, uint32_t length) { RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTable).toObject()); AutoSetNewObjectMetadata metadata(cx); @@ -872,17 +872,17 @@ WasmTableObject::create(JSContext* cx, u MOZ_ASSERT(obj->isNewborn()); TableDesc desc; desc.kind = TableKind::AnyFunction; desc.external = true; desc.initial = length; desc.maximum = length; - SharedTable table = Table::create(cx, desc); + SharedTable table = Table::create(cx, desc, obj); if (!table) return nullptr; obj->initReservedSlot(TABLE_SLOT, PrivateValue(table.forget().take())); MOZ_ASSERT(!obj->isNewborn()); return obj; }
--- a/js/src/asmjs/WasmModule.cpp +++ b/js/src/asmjs/WasmModule.cpp @@ -584,17 +584,17 @@ Module::instantiateTable(JSContext* cx, MOZ_ASSERT(tableDesc.kind == TableKind::AnyFunction); tableObj.set(WasmTableObject::create(cx, tableDesc.initial)); if (!tableObj) return false; table = &tableObj->table(); } else { - table = Table::create(cx, tableDesc); + table = Table::create(cx, tableDesc, /* HandleWasmTableObject = */ nullptr); if (!table) return false; } if (!tables->emplaceBack(table)) { ReportOutOfMemory(cx); return false; }
--- a/js/src/asmjs/WasmTable.cpp +++ b/js/src/asmjs/WasmTable.cpp @@ -16,38 +16,40 @@ * limitations under the License. */ #include "asmjs/WasmTable.h" #include "jscntxt.h" #include "asmjs/WasmInstance.h" +#include "asmjs/WasmJS.h" using namespace js; using namespace js::wasm; /* static */ SharedTable -Table::create(JSContext* cx, const TableDesc& desc) +Table::create(JSContext* cx, const TableDesc& desc, HandleWasmTableObject maybeObject) { SharedTable table = cx->new_<Table>(); if (!table) return nullptr; // The raw element type of a Table depends on whether it is external: an // external table can contain functions from multiple instances and thus // must store an additional instance pointer in each element. void* array; if (desc.external) array = cx->pod_calloc<ExternalTableElem>(desc.initial); else array = cx->pod_calloc<void*>(desc.initial); if (!array) return nullptr; + table->maybeObject_.set(maybeObject); table->array_.reset((uint8_t*)array); table->kind_ = desc.kind; table->length_ = desc.initial; table->initialized_ = false; table->external_ = desc.external; return table; } @@ -68,26 +70,50 @@ Table::init(Instance& instance) } else { void** array = internalArray(); for (uint32_t i = 0; i < length_; i++) array[i] = code; } } void -Table::trace(JSTracer* trc) +Table::tracePrivate(JSTracer* trc) { + // If this table has a WasmTableObject, then this method is only called by + // WasmTableObject's trace hook so maybeObject_ must already be marked. + // TraceEdge is called so that the pointer can be updated during a moving + // GC. TraceWeakEdge may sound better, but it is less efficient given that + // we know object_ is already marked. + if (maybeObject_) { + MOZ_ASSERT(!gc::IsAboutToBeFinalized(&maybeObject_)); + TraceEdge(trc, &maybeObject_, "wasm table object"); + } + if (!initialized_ || !external_) return; ExternalTableElem* array = externalArray(); for (uint32_t i = 0; i < length_; i++) array[i].tls->instance->trace(trc); } +void +Table::trace(JSTracer* trc) +{ + // The trace hook of WasmTableObject will call Table::tracePrivate at + // which point we can mark the rest of the children. If there is no + // WasmTableObject, call Table::tracePrivate directly. Redirecting through + // the WasmTableObject avoids marking the entire Table on each incoming + // edge (once per dependent Instance). + if (maybeObject_) + TraceEdge(trc, &maybeObject_, "wasm table object"); + else + tracePrivate(trc); +} + void** Table::internalArray() const { MOZ_ASSERT(initialized_); MOZ_ASSERT(!external_); return (void**)array_.get(); }
--- a/js/src/asmjs/WasmTable.h +++ b/js/src/asmjs/WasmTable.h @@ -25,24 +25,31 @@ namespace js { namespace wasm { // A Table is an indexable array of opaque values. Tables are first-class // stateful objects exposed to WebAssembly. asm.js also uses Tables to represent // its homogeneous function-pointer tables. class Table : public ShareableBase<Table> { - UniquePtr<uint8_t[], JS::FreePolicy> array_; - TableKind kind_; - uint32_t length_; - bool initialized_; - bool external_; + typedef UniquePtr<uint8_t[], JS::FreePolicy> UniqueByteArray; + + ReadBarrieredWasmTableObject maybeObject_; + UniqueByteArray array_; + TableKind kind_; + uint32_t length_; + bool initialized_; + bool external_; + + void tracePrivate(JSTracer* trc); + friend class js::WasmTableObject; public: - static RefPtr<Table> create(JSContext* cx, const TableDesc& desc); + static RefPtr<Table> create(JSContext* cx, const TableDesc& desc, + HandleWasmTableObject maybeObject); void trace(JSTracer* trc); // These accessors may be used before initialization. bool external() const { return external_; } bool isTypedFunction() const { return kind_ == TableKind::TypedFunction; } uint32_t length() const { return length_; } uint8_t* base() const { return array_.get(); }
--- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -537,23 +537,23 @@ intl_availableLocales(JSContext* cx, Cou if (!locales) return false; #if ENABLE_INTL_API uint32_t count = countAvailable(); RootedValue t(cx, BooleanValue(true)); for (uint32_t i = 0; i < count; i++) { const char* locale = getAvailable(i); - ScopedJSFreePtr<char> lang(JS_strdup(cx, locale)); + auto lang = DuplicateString(cx, locale); if (!lang) return false; char* p; - while ((p = strchr(lang, '_'))) + while ((p = strchr(lang.get(), '_'))) *p = '-'; - RootedAtom a(cx, Atomize(cx, lang, strlen(lang))); + RootedAtom a(cx, Atomize(cx, lang.get(), strlen(lang.get()))); if (!a) return false; if (!DefineProperty(cx, locales, a->asPropertyName(), t, nullptr, nullptr, JSPROP_ENUMERATE)) { return false; } } @@ -603,36 +603,31 @@ equal(JSAutoByteString& s1, const char* static const char* icuLocale(const char* locale) { if (equal(locale, "und")) return ""; // ICU root locale return locale; } -// Simple RAII for ICU objects. MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE -// unfortunately doesn't work because of namespace incompatibilities -// (TypeSpecificDelete cannot be in icu and mozilla at the same time) -// and because ICU declares both UNumberFormat and UDateTimePatternGenerator -// as void*. -template <typename T> +// Simple RAII for ICU objects. Unfortunately, ICU's C++ API is uniformly +// unstable, so we can't use its smart pointers for this. +template <typename T, void (Delete)(T*)> class ScopedICUObject { T* ptr_; - void (* deleter_)(T*); public: - ScopedICUObject(T* ptr, void (*deleter)(T*)) - : ptr_(ptr), - deleter_(deleter) + explicit ScopedICUObject(T* ptr) + : ptr_(ptr) {} ~ScopedICUObject() { if (ptr_) - deleter_(ptr_); + Delete(ptr_); } // In cases where an object should be deleted on abnormal exits, // but returned to the caller if everything goes well, call forget() // to transfer the object just before returning. T* forget() { T* tmp = ptr_; ptr_ = nullptr; @@ -774,25 +769,27 @@ collator_finalize(FreeOp* fop, JSObject* const Value& slot = obj->as<NativeObject>().getReservedSlot(UCOLLATOR_SLOT); if (!slot.isUndefined()) { if (UCollator* coll = static_cast<UCollator*>(slot.toPrivate())) ucol_close(coll); } } static JSObject* -InitCollatorClass(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global) +CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global) { RootedFunction ctor(cx, global->createConstructor(cx, &Collator, cx->names().Collator, 0)); if (!ctor) return nullptr; - RootedObject proto(cx, global->as<GlobalObject>().getOrCreateCollatorPrototype(cx)); + RootedNativeObject proto(cx, global->createBlankPrototype(cx, &CollatorClass)); if (!proto) return nullptr; + proto->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr)); + if (!LinkConstructorAndPrototype(cx, ctor, proto)) return nullptr; // 10.2.2 if (!JS_DefineFunctions(cx, ctor, collator_static_methods)) return nullptr; // 10.3.2 and 10.3.3 @@ -822,28 +819,17 @@ InitCollatorClass(JSContext* cx, HandleO if (!IntlInitialize(cx, proto, cx->names().InitializeCollator, UndefinedHandleValue, options)) return nullptr; // 8.1 RootedValue ctorValue(cx, ObjectValue(*ctor)); if (!DefineProperty(cx, Intl, cx->names().Collator, ctorValue, nullptr, nullptr, 0)) return nullptr; - return ctor; -} - -bool -GlobalObject::initCollatorProto(JSContext* cx, Handle<GlobalObject*> global) -{ - RootedNativeObject proto(cx, global->createBlankPrototype(cx, &CollatorClass)); - if (!proto) - return false; - proto->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr)); - global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*proto)); - return true; + return proto; } bool js::intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 0); @@ -865,17 +851,17 @@ js::intl_availableCollations(JSContext* if (!locale) return false; UErrorCode status = U_ZERO_ERROR; UEnumeration* values = ucol_getKeywordValuesForLocale("co", locale.ptr(), false, &status); if (U_FAILURE(status)) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return false; } - ScopedICUObject<UEnumeration> toClose(values, uenum_close); + ScopedICUObject<UEnumeration, uenum_close> toClose(values); uint32_t count = uenum_count(values, &status); if (U_FAILURE(status)) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return false; } RootedObject collations(cx, NewDenseEmptyArray(cx)); @@ -1273,26 +1259,28 @@ numberFormat_finalize(FreeOp* fop, JSObj const Value& slot = obj->as<NativeObject>().getReservedSlot(UNUMBER_FORMAT_SLOT); if (!slot.isUndefined()) { if (UNumberFormat* nf = static_cast<UNumberFormat*>(slot.toPrivate())) unum_close(nf); } } static JSObject* -InitNumberFormatClass(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global) +CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global) { RootedFunction ctor(cx); ctor = global->createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0); if (!ctor) return nullptr; - RootedObject proto(cx, global->as<GlobalObject>().getOrCreateNumberFormatPrototype(cx)); + RootedNativeObject proto(cx, global->createBlankPrototype(cx, &NumberFormatClass)); if (!proto) return nullptr; + proto->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); + if (!LinkConstructorAndPrototype(cx, ctor, proto)) return nullptr; // 11.2.2 if (!JS_DefineFunctions(cx, ctor, numberFormat_static_methods)) return nullptr; // 11.3.2 and 11.3.3 @@ -1328,28 +1316,17 @@ InitNumberFormatClass(JSContext* cx, Han return nullptr; } // 8.1 RootedValue ctorValue(cx, ObjectValue(*ctor)); if (!DefineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr, 0)) return nullptr; - return ctor; -} - -bool -GlobalObject::initNumberFormatProto(JSContext* cx, Handle<GlobalObject*> global) -{ - RootedNativeObject proto(cx, global->createBlankPrototype(cx, &NumberFormatClass)); - if (!proto) - return false; - proto->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); - global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*proto)); - return true; + return proto; } bool js::intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 0); @@ -1507,17 +1484,17 @@ NewUNumberFormat(JSContext* cx, HandleOb uUseGrouping = value.toBoolean(); UErrorCode status = U_ZERO_ERROR; UNumberFormat* nf = unum_open(uStyle, nullptr, 0, icuLocale(locale.ptr()), nullptr, &status); if (U_FAILURE(status)) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return nullptr; } - ScopedICUObject<UNumberFormat> toClose(nf, unum_close); + ScopedICUObject<UNumberFormat, unum_close> toClose(nf); if (uCurrency) { unum_setTextAttribute(nf, UNUM_CURRENCY_CODE, uCurrency, 3, &status); if (U_FAILURE(status)) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return nullptr; } } @@ -1746,26 +1723,28 @@ dateTimeFormat_finalize(FreeOp* fop, JSO const Value& slot = obj->as<NativeObject>().getReservedSlot(UDATE_FORMAT_SLOT); if (!slot.isUndefined()) { if (UDateFormat* df = static_cast<UDateFormat*>(slot.toPrivate())) udat_close(df); } } static JSObject* -InitDateTimeFormatClass(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global) +CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global) { RootedFunction ctor(cx); ctor = global->createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0); if (!ctor) return nullptr; - RootedObject proto(cx, global->as<GlobalObject>().getOrCreateDateTimeFormatPrototype(cx)); + RootedNativeObject proto(cx, global->createBlankPrototype(cx, &DateTimeFormatClass)); if (!proto) return nullptr; + proto->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr)); + if (!LinkConstructorAndPrototype(cx, ctor, proto)) return nullptr; // 12.2.2 if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods)) return nullptr; // 12.3.2 and 12.3.3 @@ -1816,28 +1795,17 @@ InitDateTimeFormatClass(JSContext* cx, H return nullptr; } // 8.1 RootedValue ctorValue(cx, ObjectValue(*ctor)); if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0)) return nullptr; - return ctor; -} - -bool -GlobalObject::initDateTimeFormatProto(JSContext* cx, Handle<GlobalObject*> global) -{ - RootedNativeObject proto(cx, global->createBlankPrototype(cx, &DateTimeFormatClass)); - if (!proto) - return false; - proto->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr)); - global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*proto)); - return true; + return proto; } bool js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 0); @@ -1875,46 +1843,54 @@ js::intl_availableCalendars(JSContext* c RootedObject calendars(cx, NewDenseEmptyArray(cx)); if (!calendars) return false; uint32_t index = 0; // We need the default calendar for the locale as the first result. UErrorCode status = U_ZERO_ERROR; - UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status); - const char* calendar = ucal_getType(cal, &status); - if (U_FAILURE(status)) { - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); - return false; + RootedString jscalendar(cx); + { + UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status); + + // This correctly handles nullptr |cal| when opening failed. + ScopedICUObject<UCalendar, ucal_close> closeCalendar(cal); + + const char* calendar = ucal_getType(cal, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar)); + if (!jscalendar) + return false; } - ucal_close(cal); - RootedString jscalendar(cx, JS_NewStringCopyZ(cx, bcp47CalendarName(calendar))); - if (!jscalendar) - return false; + RootedValue element(cx, StringValue(jscalendar)); if (!DefineElement(cx, calendars, index++, element)) return false; // Now get the calendars that "would make a difference", i.e., not the default. UEnumeration* values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status); if (U_FAILURE(status)) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return false; } - ScopedICUObject<UEnumeration> toClose(values, uenum_close); + ScopedICUObject<UEnumeration, uenum_close> toClose(values); uint32_t count = uenum_count(values, &status); if (U_FAILURE(status)) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return false; } for (; count > 0; count--) { - calendar = uenum_next(values, nullptr, &status); + const char* calendar = uenum_next(values, nullptr, &status); if (U_FAILURE(status)) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return false; } jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar)); if (!jscalendar) return false; @@ -1951,17 +1927,17 @@ js::intl_patternForSkeleton(JSContext* c uint32_t skeletonLen = u_strlen(Char16ToUChar(skeletonChars.start().get())); UErrorCode status = U_ZERO_ERROR; UDateTimePatternGenerator* gen = udatpg_open(icuLocale(locale.ptr()), &status); if (U_FAILURE(status)) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return false; } - ScopedICUObject<UDateTimePatternGenerator> toClose(gen, udatpg_close); + ScopedICUObject<UDateTimePatternGenerator, udatpg_close> toClose(gen); int32_t size = udatpg_getBestPattern(gen, Char16ToUChar(skeletonChars.start().get()), skeletonLen, nullptr, 0, &status); if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return false; } ScopedJSFreePtr<UChar> pattern(cx->pod_malloc<UChar>(size + 1)); @@ -2384,56 +2360,76 @@ static const JSFunctionSpec intl_static_ JS_SELF_HOSTED_FN("getCanonicalLocales", "Intl_getCanonicalLocales", 1, 0), JS_FS_END }; /** * Initializes the Intl Object and its standard built-in properties. * Spec: ECMAScript Internationalization API Specification, 8.0, 8.1 */ +bool +GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global) +{ + RootedObject proto(cx, global->getOrCreateObjectPrototype(cx)); + if (!proto) + return false; + + // The |Intl| object is just a plain object with some "static" function + // properties and some constructor properties. + RootedObject intl(cx, NewObjectWithGivenProto(cx, &IntlClass, proto, SingletonObject)); + if (!intl) + return false; + + // Add the static functions. + if (!JS_DefineFunctions(cx, intl, intl_static_methods)) + return false; + + // Add the constructor properties, computing and returning the relevant + // prototype objects needed below. + RootedObject collatorProto(cx, CreateCollatorPrototype(cx, intl, global)); + if (!collatorProto) + return false; + RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global)); + if (!dateTimeFormatProto) + return false; + RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global)); + if (!numberFormatProto) + return false; + + // The |Intl| object is fully set up now, so define the global property. + RootedValue intlValue(cx, ObjectValue(*intl)); + if (!DefineProperty(cx, global, cx->names().Intl, intlValue, nullptr, nullptr, + JSPROP_RESOLVING)) + { + return false; + } + + // Now that the |Intl| object is successfully added, we can OOM-safely fill + // in all relevant reserved global slots. + + // Cache the various prototypes, for use in creating instances of these + // objects with the proper [[Prototype]] as "the original value of + // |Intl.Collator.prototype|" and similar. For builtin classes like + // |String.prototype| we have |JSProto_*| that enables + // |getPrototype(JSProto_*)|, but that has global-object-property-related + // baggage we don't need or want, so we use one-off reserved slots. + global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto)); + global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto)); + global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto)); + + // Also cache |Intl| to implement spec language that conditions behavior + // based on values being equal to "the standard built-in |Intl| object". + // Use |setConstructor| to correspond with |JSProto_Intl|. + // + // XXX We should possibly do a one-off reserved slot like above. + global->setConstructor(JSProto_Intl, ObjectValue(*intl)); + return true; +} + JSObject* js::InitIntlClass(JSContext* cx, HandleObject obj) { - MOZ_ASSERT(obj->is<GlobalObject>()); - Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); - - // The constructors above need to be able to determine whether they've been - // called with this being "the standard built-in Intl object". The global - // object reserves slots to track standard built-in objects, but doesn't - // normally keep references to non-constructors. This makes sure there is one. - RootedObject Intl(cx, global->getOrCreateIntlObject(cx)); - if (!Intl) - return nullptr; - - RootedValue IntlValue(cx, ObjectValue(*Intl)); - if (!DefineProperty(cx, global, cx->names().Intl, IntlValue, nullptr, nullptr, - JSPROP_RESOLVING)) - { - return nullptr; - } - - if (!JS_DefineFunctions(cx, Intl, intl_static_methods)) + Handle<GlobalObject*> global = obj.as<GlobalObject>(); + if (!GlobalObject::initIntlObject(cx, global)) return nullptr; - if (!InitCollatorClass(cx, Intl, global)) - return nullptr; - if (!InitNumberFormatClass(cx, Intl, global)) - return nullptr; - if (!InitDateTimeFormatClass(cx, Intl, global)) - return nullptr; - - global->setConstructor(JSProto_Intl, ObjectValue(*Intl)); - - return Intl; + return &global->getConstructor(JSProto_Intl).toObject(); } - -bool -GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global) -{ - RootedObject Intl(cx); - RootedObject proto(cx, global->getOrCreateObjectPrototype(cx)); - Intl = NewObjectWithGivenProto(cx, &IntlClass, proto, SingletonObject); - if (!Intl) - return false; - - global->setConstructor(JSProto_Intl, ObjectValue(*Intl)); - return true; -}
--- a/js/src/gc/Barrier.h +++ b/js/src/gc/Barrier.h @@ -904,16 +904,17 @@ class ArrayBufferObject; class NestedScopeObject; class DebugScopeObject; class GlobalObject; class ScriptSourceObject; class Shape; class BaseShape; class UnownedBaseShape; class WasmInstanceObject; +class WasmTableObject; namespace jit { class JitCode; } // namespace jit typedef PreBarriered<JSObject*> PreBarrieredObject; typedef PreBarriered<JSScript*> PreBarrieredScript; typedef PreBarriered<jit::JitCode*> PreBarrieredJitCode; typedef PreBarriered<JSString*> PreBarrieredString; @@ -956,14 +957,15 @@ typedef ReadBarriered<JSObject*> ReadBar typedef ReadBarriered<JSFunction*> ReadBarrieredFunction; typedef ReadBarriered<JSScript*> ReadBarrieredScript; typedef ReadBarriered<ScriptSourceObject*> ReadBarrieredScriptSourceObject; typedef ReadBarriered<Shape*> ReadBarrieredShape; typedef ReadBarriered<jit::JitCode*> ReadBarrieredJitCode; typedef ReadBarriered<ObjectGroup*> ReadBarrieredObjectGroup; typedef ReadBarriered<JS::Symbol*> ReadBarrieredSymbol; typedef ReadBarriered<WasmInstanceObject*> ReadBarrieredWasmInstanceObject; +typedef ReadBarriered<WasmTableObject*> ReadBarrieredWasmTableObject; typedef ReadBarriered<Value> ReadBarrieredValue; } /* namespace js */ #endif /* gc_Barrier_h */
--- a/js/src/gc/Policy.h +++ b/js/src/gc/Policy.h @@ -103,16 +103,17 @@ class JitCode; D(js::ScopeObject*) \ D(js::ScriptSourceObject*) \ D(js::Shape*) \ D(js::SharedArrayBufferObject*) \ D(js::StructTypeDescr*) \ D(js::UnownedBaseShape*) \ D(js::WasmInstanceObject*) \ D(js::WasmMemoryObject*) \ + D(js::WasmTableObject*) \ D(js::jit::JitCode*) // Expand the given macro D for each internal tagged GC pointer type. #define FOR_EACH_INTERNAL_TAGGED_GC_POINTER_TYPE(D) \ D(js::TaggedProto) // Expand the macro D for every GC reference type that we know about. #define FOR_EACH_GC_POINTER_TYPE(D) \
--- a/js/src/jit-test/tests/wasm/table-gc.js +++ b/js/src/jit-test/tests/wasm/table-gc.js @@ -14,17 +14,18 @@ const Table = WebAssembly.Table; const textToBinary = str => wasmTextToBinary(str, 'new-format'); const evalText = (str, imports) => new Instance(new Module(textToBinary(str)), imports); var caller = `(type $v2i (func (result i32))) (func $call (param $i i32) (result i32) (call_indirect $v2i (get_local $i))) (export "call" $call)` var callee = i => `(func $f${i} (type $v2i) (result i32) (i32.const ${i}))`; // A table should not hold exported functions alive and exported functions // should not hold their originating table alive. Live exported functions should -// hold instances alive. Nothing should hold the export object alive. +// hold instances alive and instances hold imported tables alive. Nothing +// should hold the export object alive. resetFinalizeCount(); var i = evalText(`(module (table (resizable 2)) (export "tbl" table) (elem (i32.const 0) $f0) ${callee(0)} ${caller})`); var e = i.exports; var t = e.tbl; var f = t.get(0); assertEq(f(), e.call(0)); assertErrorMessage(() => e.call(1), Error, /bad wasm indirect call/); assertErrorMessage(() => e.call(2), Error, /out-of-range/); @@ -43,20 +44,20 @@ f.edge = makeFinalizeObserver(); gc(); assertEq(finalizeCount(), 1); i.exports = null; e = null; gc(); assertEq(finalizeCount(), 2); t = null; gc(); -assertEq(finalizeCount(), 3); +assertEq(finalizeCount(), 2); i = null; gc(); -assertEq(finalizeCount(), 3); +assertEq(finalizeCount(), 2); assertEq(f(), 0); f = null; gc(); assertEq(finalizeCount(), 5); // A table should hold the instance of any of its elements alive. resetFinalizeCount(); var i = evalText(`(module (table (resizable 1)) (export "tbl" table) (elem (i32.const 0) $f0) ${callee(0)} ${caller})`); @@ -191,17 +192,17 @@ assertEq(finalizeCount(), 5); // there are no outstanding references. resetFinalizeCount(); const N = 10; var tbl = new Table({initial:N, element:"anyfunc"}); tbl.edge = makeFinalizeObserver(); function runTest() { tbl = null; gc(); - assertEq(finalizeCount(), 1); + assertEq(finalizeCount(), 0); return 100; } var i = evalText( `(module (import "a" "b" (result i32)) (func $f (param i32) (result i32) (call_import 0)) (export "f" $f) )`,
--- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -598,17 +598,17 @@ IonBuilder::analyzeNewLoopTypes(MBasicBl MDefinition* oldDef = oldEntryRp->getOperand(slot); if (!oldDef->isPhi()) { MOZ_ASSERT(oldDef->block()->id() < oldEntry->id()); MOZ_ASSERT(oldDef == entry->getSlot(slot)); continue; } MPhi* oldPhi = oldDef->toPhi(); MPhi* newPhi = entry->getSlot(slot)->toPhi(); - if (!newPhi->addBackedgeType(oldPhi->type(), oldPhi->resultTypeSet())) + if (!newPhi->addBackedgeType(alloc(), oldPhi->type(), oldPhi->resultTypeSet())) return false; } } // Update the most recent header for this loop encountered, in case // new types flow to the phis and the loop is processed at least // three times. loopHeaders_[i].header = entry; @@ -639,27 +639,27 @@ IonBuilder::analyzeNewLoopTypes(MBasicBl if (*last == JSOP_POS) last = earlier; if (CodeSpec[*last].format & JOF_TYPESET) { TemporaryTypeSet* typeSet = bytecodeTypes(last); if (!typeSet->empty()) { MIRType type = typeSet->getKnownMIRType(); - if (!phi->addBackedgeType(type, typeSet)) + if (!phi->addBackedgeType(alloc(), type, typeSet)) return false; } } else if (*last == JSOP_GETLOCAL || *last == JSOP_GETARG) { uint32_t slot = (*last == JSOP_GETLOCAL) ? info().localSlot(GET_LOCALNO(last)) : info().argSlotUnchecked(GET_ARGNO(last)); if (slot < info().firstStackSlot()) { MPhi* otherPhi = entry->getSlot(slot)->toPhi(); if (otherPhi->hasBackedgeType()) { - if (!phi->addBackedgeType(otherPhi->type(), otherPhi->resultTypeSet())) + if (!phi->addBackedgeType(alloc(), otherPhi->type(), otherPhi->resultTypeSet())) return false; } } } else { MIRType type = MIRType::None; switch (*last) { case JSOP_VOID: case JSOP_UNDEFINED: @@ -721,17 +721,17 @@ IonBuilder::analyzeNewLoopTypes(MBasicBl case JSOP_MOD: case JSOP_NEG: type = inspector->expectedResultType(last); break; default: break; } if (type != MIRType::None) { - if (!phi->addBackedgeType(type, nullptr)) + if (!phi->addBackedgeType(alloc(), type, nullptr)) return false; } } } return true; } bool @@ -2448,17 +2448,17 @@ IonBuilder::finishLoop(CFGState& state, MOZ_ASSERT(current); MOZ_ASSERT(loopDepth_); loopDepth_--; MOZ_ASSERT_IF(successor, successor->loopDepth() == loopDepth_); // Compute phis in the loop header and propagate them throughout the loop, // including the successor. - AbortReason r = state.loop.entry->setBackedge(current); + AbortReason r = state.loop.entry->setBackedge(alloc(), current); if (r == AbortReason_Alloc) return ControlStatus_Error; if (r == AbortReason_Disable) { // If there are types for variables on the backedge that were not // present at the original loop header, then uses of the variables' // phis may have generated incorrect nodes. The new types have been // incorporated into the header phis, so remove all blocks for the // loop body and restart with the new types. @@ -7977,17 +7977,17 @@ IonBuilder::newPendingLoopHeader(MBasicB // Extract typeset from value. LifoAlloc* lifoAlloc = alloc().lifoAlloc(); TemporaryTypeSet* typeSet = lifoAlloc->new_<TemporaryTypeSet>(lifoAlloc, existingType); if (!typeSet) return nullptr; MIRType type = typeSet->getKnownMIRType(); - if (!phi->addBackedgeType(type, typeSet)) + if (!phi->addBackedgeType(alloc(), type, typeSet)) return nullptr; } } return block; } MTest*
--- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -1075,17 +1075,17 @@ class IonBuilder TemporaryTypeSet* bytecodeTypes(jsbytecode* pc); // Use one of the below methods for updating the current block, rather than // updating |current| directly. setCurrent() should only be used in cases // where the block cannot have phis whose type needs to be computed. MOZ_MUST_USE bool setCurrentAndSpecializePhis(MBasicBlock* block) { if (block) { - if (!block->specializePhis()) + if (!block->specializePhis(alloc())) return false; } setCurrent(block); return true; } void setCurrent(MBasicBlock* block) { current = block;
--- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -2448,56 +2448,55 @@ MPhi::congruentTo(const MDefinition* ins // For now, consider phis in different blocks incongruent. if (ins->block() != block()) return false; return congruentIfOperandsEqual(ins); } static inline TemporaryTypeSet* -MakeMIRTypeSet(MIRType type) +MakeMIRTypeSet(TempAllocator& alloc, MIRType type) { MOZ_ASSERT(type != MIRType::Value); TypeSet::Type ntype = type == MIRType::Object ? TypeSet::AnyObjectType() : TypeSet::PrimitiveType(ValueTypeFromMIRType(type)); - LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc(); - return alloc->new_<TemporaryTypeSet>(alloc, ntype); + return alloc.lifoAlloc()->new_<TemporaryTypeSet>(alloc.lifoAlloc(), ntype); } bool -jit::MergeTypes(MIRType* ptype, TemporaryTypeSet** ptypeSet, +jit::MergeTypes(TempAllocator& alloc, MIRType* ptype, TemporaryTypeSet** ptypeSet, MIRType newType, TemporaryTypeSet* newTypeSet) { if (newTypeSet && newTypeSet->empty()) return true; + LifoAlloc::AutoFallibleScope fallibleAllocator(alloc.lifoAlloc()); if (newType != *ptype) { if (IsTypeRepresentableAsDouble(newType) && IsTypeRepresentableAsDouble(*ptype)) { *ptype = MIRType::Double; } else if (*ptype != MIRType::Value) { if (!*ptypeSet) { - *ptypeSet = MakeMIRTypeSet(*ptype); + *ptypeSet = MakeMIRTypeSet(alloc, *ptype); if (!*ptypeSet) return false; } *ptype = MIRType::Value; } else if (*ptypeSet && (*ptypeSet)->empty()) { *ptype = newType; } } if (*ptypeSet) { - LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc(); if (!newTypeSet && newType != MIRType::Value) { - newTypeSet = MakeMIRTypeSet(newType); + newTypeSet = MakeMIRTypeSet(alloc, newType); if (!newTypeSet) return false; } if (newTypeSet) { if (!newTypeSet->isSubset(*ptypeSet)) { - *ptypeSet = TypeSet::unionSets(*ptypeSet, newTypeSet, alloc); + *ptypeSet = TypeSet::unionSets(*ptypeSet, newTypeSet, alloc.lifoAlloc()); if (!*ptypeSet) return false; } } else { *ptypeSet = nullptr; } } return true; @@ -2588,17 +2587,17 @@ jit::CanStoreUnboxedType(TempAllocator& static bool CanStoreUnboxedType(TempAllocator& alloc, JSValueType unboxedType, MDefinition* value) { return CanStoreUnboxedType(alloc, unboxedType, value->type(), value->resultTypeSet()); } bool -MPhi::specializeType() +MPhi::specializeType(TempAllocator& alloc) { #ifdef DEBUG MOZ_ASSERT(!specialized_); specialized_ = true; #endif MOZ_ASSERT(!inputs_.empty()); @@ -2613,35 +2612,35 @@ MPhi::specializeType() start = 1; } MIRType resultType = this->type(); TemporaryTypeSet* resultTypeSet = this->resultTypeSet(); for (size_t i = start; i < inputs_.length(); i++) { MDefinition* def = getOperand(i); - if (!MergeTypes(&resultType, &resultTypeSet, def->type(), def->resultTypeSet())) + if (!MergeTypes(alloc, &resultType, &resultTypeSet, def->type(), def->resultTypeSet())) return false; } setResultType(resultType); setResultTypeSet(resultTypeSet); return true; } bool -MPhi::addBackedgeType(MIRType type, TemporaryTypeSet* typeSet) +MPhi::addBackedgeType(TempAllocator& alloc, MIRType type, TemporaryTypeSet* typeSet) { MOZ_ASSERT(!specialized_); if (hasBackedgeType_) { MIRType resultType = this->type(); TemporaryTypeSet* resultTypeSet = this->resultTypeSet(); - if (!MergeTypes(&resultType, &resultTypeSet, type, typeSet)) + if (!MergeTypes(alloc, &resultType, &resultTypeSet, type, typeSet)) return false; setResultType(resultType); setResultTypeSet(resultTypeSet); } else { setResultType(type); setResultTypeSet(typeSet); hasBackedgeType_ = true; @@ -2668,22 +2667,22 @@ MPhi::typeIncludes(MDefinition* def) return this->type() == MIRType::Value && (!this->resultTypeSet() || this->resultTypeSet()->unknown()); } return this->mightBeType(def->type()); } bool -MPhi::checkForTypeChange(MDefinition* ins, bool* ptypeChange) +MPhi::checkForTypeChange(TempAllocator& alloc, MDefinition* ins, bool* ptypeChange) { MIRType resultType = this->type(); TemporaryTypeSet* resultTypeSet = this->resultTypeSet(); - if (!MergeTypes(&resultType, &resultTypeSet, ins->type(), ins->resultTypeSet())) + if (!MergeTypes(alloc, &resultType, &resultTypeSet, ins->type(), ins->resultTypeSet())) return false; if (resultType != this->type() || resultTypeSet != this->resultTypeSet()) { *ptypeChange = true; setResultType(resultType); setResultTypeSet(resultTypeSet); } return true;
--- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -3094,17 +3094,17 @@ class MThrow // Fabricate a type set containing only the type of the specified object. TemporaryTypeSet* MakeSingletonTypeSet(CompilerConstraintList* constraints, JSObject* obj); TemporaryTypeSet* MakeSingletonTypeSet(CompilerConstraintList* constraints, ObjectGroup* obj); MOZ_MUST_USE bool -MergeTypes(MIRType* ptype, TemporaryTypeSet** ptypeSet, +MergeTypes(TempAllocator& alloc, MIRType* ptype, TemporaryTypeSet** ptypeSet, MIRType newType, TemporaryTypeSet* newTypeSet); bool TypeSetIncludes(TypeSet* types, MIRType input, TypeSet* inputTypes); bool EqualTypes(MIRType type1, TemporaryTypeSet* typeset1, MIRType type2, TemporaryTypeSet* typeset2); @@ -7375,17 +7375,17 @@ class MPhi final } bool triedToSpecialize() const { return triedToSpecialize_; } void specialize(MIRType type) { triedToSpecialize_ = true; setResultType(type); } - bool specializeType(); + bool specializeType(TempAllocator& alloc); #ifdef DEBUG // Assert that this is a phi in a loop header with a unique predecessor and // a unique backedge. void assertLoopPhi() const; #else void assertLoopPhi() const {} #endif @@ -7404,17 +7404,18 @@ class MPhi final return getOperand(1); } // Whether this phi's type already includes information for def. bool typeIncludes(MDefinition* def); // Add types for this phi which speculate about new inputs that may come in // via a loop backedge. - MOZ_MUST_USE bool addBackedgeType(MIRType type, TemporaryTypeSet* typeSet); + MOZ_MUST_USE bool addBackedgeType(TempAllocator& alloc, MIRType type, + TemporaryTypeSet* typeSet); // Initializes the operands vector to the given capacity, // permitting use of addInput() instead of addInputSlow(). MOZ_MUST_USE bool reserveLength(size_t length) { return inputs_.reserve(length); } // Use only if capacity has been reserved by reserveLength @@ -7432,17 +7433,17 @@ class MPhi final // we know the inputs fits in the vector's inline storage. void addInlineInput(MDefinition* ins) { MOZ_ASSERT(inputs_.length() < InputVector::InlineLength); MOZ_ALWAYS_TRUE(addInputSlow(ins)); } // Update the type of this phi after adding |ins| as an input. Set // |*ptypeChange| to true if the type changed. - bool checkForTypeChange(MDefinition* ins, bool* ptypeChange); + bool checkForTypeChange(TempAllocator& alloc, MDefinition* ins, bool* ptypeChange); MDefinition* foldsTo(TempAllocator& alloc) override; MDefinition* foldsTernary(); MDefinition* foldsFilterTypeSet(); bool congruentTo(const MDefinition* ins) const override; bool isIterator() const {
--- a/js/src/jit/MIRGraph.cpp +++ b/js/src/jit/MIRGraph.cpp @@ -1278,30 +1278,30 @@ MBasicBlock::assertUsesAreNotWithin(MUse for (; use != end; use++) { MOZ_ASSERT_IF(use->consumer()->isDefinition(), use->consumer()->toDefinition()->block()->id() < id()); } #endif } AbortReason -MBasicBlock::setBackedge(MBasicBlock* pred) +MBasicBlock::setBackedge(TempAllocator& alloc, MBasicBlock* pred) { // Predecessors must be finished, and at the correct stack depth. MOZ_ASSERT(hasLastIns()); MOZ_ASSERT(pred->hasLastIns()); MOZ_ASSERT(pred->stackDepth() == entryResumePoint()->stackDepth()); // We must be a pending loop header MOZ_ASSERT(kind_ == PENDING_LOOP_HEADER); bool hadTypeChange = false; // Add exit definitions to each corresponding phi at the entry. - if (!inheritPhisFromBackedge(pred, &hadTypeChange)) + if (!inheritPhisFromBackedge(alloc, pred, &hadTypeChange)) return AbortReason_Alloc; if (hadTypeChange) { for (MPhiIterator phi = phisBegin(); phi != phisEnd(); phi++) phi->removeOperand(phi->numOperands() - 1); return AbortReason_Disable; } @@ -1547,17 +1547,17 @@ MBasicBlock::inheritPhis(MBasicBlock* he // If the entryDef is the same as exitDef, then we must propagate the // phi down to this successor. This chance was missed as part of // setBackedge() because exits are not captured in resume points. setSlot(slot, phi); } } bool -MBasicBlock::inheritPhisFromBackedge(MBasicBlock* backedge, bool* hadTypeChange) +MBasicBlock::inheritPhisFromBackedge(TempAllocator& alloc, MBasicBlock* backedge, bool* hadTypeChange) { // We must be a pending loop header MOZ_ASSERT(kind_ == PENDING_LOOP_HEADER); size_t stackDepth = entryResumePoint()->stackDepth(); for (size_t slot = 0; slot < stackDepth; slot++) { // Get the value stack-slot of the back edge. MDefinition* exitDef = backedge->getSlot(slot); @@ -1588,31 +1588,31 @@ MBasicBlock::inheritPhisFromBackedge(MBa // onto phis. exitDef = entryDef->getOperand(0); } bool typeChange = false; if (!entryDef->addInputSlow(exitDef)) return false; - if (!entryDef->checkForTypeChange(exitDef, &typeChange)) + if (!entryDef->checkForTypeChange(alloc, exitDef, &typeChange)) return false; *hadTypeChange |= typeChange; setSlot(slot, entryDef); } return true; } bool -MBasicBlock::specializePhis() +MBasicBlock::specializePhis(TempAllocator& alloc) { for (MPhiIterator iter = phisBegin(); iter != phisEnd(); iter++) { MPhi* phi = *iter; - if (!phi->specializeType()) + if (!phi->specializeType(alloc)) return false; } return true; } MTest* MBasicBlock::immediateDominatorBranch(BranchDirection* pdirection) {
--- a/js/src/jit/MIRGraph.h +++ b/js/src/jit/MIRGraph.h @@ -250,36 +250,37 @@ class MBasicBlock : public TempObject, p void removePredecessorWithoutPhiOperands(MBasicBlock* pred, size_t predIndex); // Resets all the dominator info so that it can be recomputed. void clearDominatorInfo(); // Sets a back edge. This places phi nodes and rewrites instructions within // the current loop as necessary. If the backedge introduces new types for // phis at the loop header, returns a disabling abort. - MOZ_MUST_USE AbortReason setBackedge(MBasicBlock* block); + MOZ_MUST_USE AbortReason setBackedge(TempAllocator& alloc, MBasicBlock* block); MOZ_MUST_USE bool setBackedgeAsmJS(MBasicBlock* block); // Resets a LOOP_HEADER block to a NORMAL block. This is needed when // optimizations remove the backedge. void clearLoopHeader(); // Sets a block to a LOOP_HEADER block, with newBackedge as its backedge. // This is needed when optimizations remove the normal entry to a loop // with multiple entries. void setLoopHeader(MBasicBlock* newBackedge); // Propagates phis placed in a loop header down to this successor block. void inheritPhis(MBasicBlock* header); // Propagates backedge slots into phis operands of the loop header. - MOZ_MUST_USE bool inheritPhisFromBackedge(MBasicBlock* backedge, bool* hadTypeChange); + MOZ_MUST_USE bool inheritPhisFromBackedge(TempAllocator& alloc, MBasicBlock* backedge, + bool* hadTypeChange); // Compute the types for phis in this block according to their inputs. - MOZ_MUST_USE bool specializePhis(); + MOZ_MUST_USE bool specializePhis(TempAllocator& alloc); void insertBefore(MInstruction* at, MInstruction* ins); void insertAfter(MInstruction* at, MInstruction* ins); void insertAtEnd(MInstruction* ins); // Add an instruction to this block, from elsewhere in the graph. void addFromElsewhere(MInstruction* ins);
--- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3888,19 +3888,16 @@ GCRuntime::beginMarkPhase(JS::gcreason:: /* Unmark everything in the zones being collected. */ zone->arenas.unmarkAll(); } for (GCZonesIter zone(rt); !zone.done(); zone.next()) { /* Unmark all weak maps in the zones being collected. */ WeakMapBase::unmarkZone(zone); } - - if (isFull) - UnmarkScriptData(rt, lock); } markRuntime(gcmarker, MarkRuntime, lock); gcstats::AutoPhase ap2(stats, gcstats::PHASE_MARK_ROOTS); if (isIncremental) { gcstats::AutoPhase ap3(stats, gcstats::PHASE_BUFFER_GRAY_ROOTS); @@ -5416,18 +5413,17 @@ GCRuntime::endSweepPhase(bool destroying gcstats::AutoPhase ap(stats, gcstats::PHASE_DESTROY); /* * Sweep script filenames after sweeping functions in the generic loop * above. In this way when a scripted function's finalizer destroys the * script and calls rt->destroyScriptHook, the hook can still access the * script's filename. See bug 323267. */ - if (isFull) - SweepScriptData(rt, lock); + SweepScriptData(rt, lock); /* Clear out any small pools that we're hanging on to. */ if (jit::JitRuntime* jitRuntime = rt->jitRuntime()) { jitRuntime->execAlloc().purge(); jitRuntime->backedgeExecAlloc().purge(); } }
--- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -581,19 +581,16 @@ FindScopeObjectIndex(JSScript* script, N for (unsigned i = 0; i < length; ++i) { if (vector[i] == &scope) return i; } MOZ_CRASH("Scope not found"); } -static bool -SaveSharedScriptData(ExclusiveContext*, Handle<JSScript*>, SharedScriptData*, uint32_t); - enum XDRClassKind { CK_BlockObject = 0, CK_WithObject = 1, CK_RegexpObject = 2, CK_JSFunction = 3, CK_JSObject = 4 }; @@ -875,17 +872,16 @@ js::XDRScript(XDRState<mode>* xdr, Handl if (!JSScript::partiallyInit(cx, script, nconsts, nobjects, ntrynotes, nblockscopes, nyieldoffsets, nTypeSets)) { return false; } MOZ_ASSERT(!script->mainOffset()); script->mainOffset_ = prologueLength; - script->setLength(length); script->funLength_ = funLength; scriptp.set(script); if (scriptBits & (1 << Strict)) script->strict_ = true; if (scriptBits & (1 << ExplicitUseStrict)) script->explicitUseStrict_ = true; @@ -949,50 +945,47 @@ js::XDRScript(XDRState<mode>* xdr, Handl } if (mode == XDR_DECODE) { script->lineno_ = lineno; script->column_ = column; script->nslots_ = nslots; } - jsbytecode* code = script->code(); - SharedScriptData* ssd; + auto scriptDataGuard = mozilla::MakeScopeExit([&] { + if (mode == XDR_DECODE) + script->freeScriptData(); + }); + if (mode == XDR_DECODE) { - ssd = SharedScriptData::new_(cx, length, nsrcnotes, natoms); - if (!ssd) + if (!script->createScriptData(cx, length, nsrcnotes, natoms)) return false; - code = ssd->data; - if (natoms != 0) { - script->natoms_ = natoms; - script->atoms = ssd->atoms(); - } } + jsbytecode* code = script->code(); if (!xdr->codeBytes(code, length) || !xdr->codeBytes(code + length, nsrcnotes)) { - if (mode == XDR_DECODE) - js_free(ssd); return false; } for (i = 0; i != natoms; ++i) { if (mode == XDR_DECODE) { RootedAtom tmp(cx); if (!XDRAtom(xdr, &tmp)) return false; - script->atoms[i].init(tmp); + script->atoms()[i].init(tmp); } else { - RootedAtom tmp(cx, script->atoms[i]); + RootedAtom tmp(cx, script->atoms()[i]); if (!XDRAtom(xdr, &tmp)) return false; } } + scriptDataGuard.release(); if (mode == XDR_DECODE) { - if (!SaveSharedScriptData(cx, script, ssd, nsrcnotes)) + if (!script->shareScriptData(cx)) return false; } if (nconsts) { GCPtrValue* vector = script->consts()->vector; RootedValue val(cx); for (i = 0; i != nconsts; ++i) { if (mode == XDR_ENCODE) @@ -2411,149 +2404,139 @@ ScriptSource::setSourceMapURL(ExclusiveC /* * Shared script data management. */ SharedScriptData* js::SharedScriptData::new_(ExclusiveContext* cx, uint32_t codeLength, uint32_t srcnotesLength, uint32_t natoms) { - /* - * Ensure the atoms are aligned, as some architectures don't allow unaligned - * access. - */ - const uint32_t pointerSize = sizeof(JSAtom*); - const uint32_t pointerMask = pointerSize - 1; - const uint32_t dataOffset = offsetof(SharedScriptData, data); - uint32_t baseLength = codeLength + srcnotesLength; - uint32_t padding = (pointerSize - ((baseLength + dataOffset) & pointerMask)) & pointerMask; - uint32_t length = baseLength + padding + pointerSize * natoms; - - SharedScriptData* entry = reinterpret_cast<SharedScriptData*>( - cx->zone()->pod_malloc<uint8_t>(length + dataOffset)); + uint32_t dataLength = natoms * sizeof(GCPtrAtom) + codeLength + srcnotesLength; + uint32_t allocLength = offsetof(SharedScriptData, data_) + dataLength; + auto entry = reinterpret_cast<SharedScriptData*>(cx->zone()->pod_malloc<uint8_t>(allocLength)); if (!entry) { ReportOutOfMemory(cx); return nullptr; } - entry->length = length; - entry->natoms = natoms; - entry->marked = false; - memset(entry->data + baseLength, 0, padding); + entry->refCount_ = 0; + entry->dataLength_ = dataLength; + entry->natoms_ = natoms; + entry->codeLength_ = codeLength; /* * Call constructors to initialize the storage that will be accessed as a * GCPtrAtom array via atoms(). */ GCPtrAtom* atoms = entry->atoms(); - MOZ_ASSERT(reinterpret_cast<uintptr_t>(atoms) % sizeof(JSAtom*) == 0); + MOZ_ASSERT(reinterpret_cast<uintptr_t>(atoms) % sizeof(GCPtrAtom*) == 0); for (unsigned i = 0; i < natoms; ++i) new (&atoms[i]) GCPtrAtom(); return entry; } +bool +JSScript::createScriptData(ExclusiveContext* cx, uint32_t codeLength, uint32_t srcnotesLength, + uint32_t natoms) +{ + MOZ_ASSERT(!scriptData()); + SharedScriptData* ssd = SharedScriptData::new_(cx, codeLength, srcnotesLength, natoms); + if (!ssd) + return false; + + setScriptData(ssd); + return true; +} + +void +JSScript::freeScriptData() +{ + MOZ_ASSERT(scriptData_->refCount() == 1); + scriptData_->decRefCount(); + scriptData_ = nullptr; +} + +void +JSScript::setScriptData(js::SharedScriptData* data) +{ + MOZ_ASSERT(!scriptData_); + scriptData_ = data; + scriptData_->incRefCount(); +} + /* * Takes ownership of its *ssd parameter and either adds it into the runtime's * ScriptDataTable or frees it if a matching entry already exists. * * Sets the |code| and |atoms| fields on the given JSScript. */ -static bool -SaveSharedScriptData(ExclusiveContext* cx, Handle<JSScript*> script, SharedScriptData* ssd, - uint32_t nsrcnotes) +bool +JSScript::shareScriptData(ExclusiveContext* cx) { - MOZ_ASSERT(script != nullptr); - MOZ_ASSERT(ssd != nullptr); + SharedScriptData* ssd = scriptData(); + MOZ_ASSERT(ssd); + MOZ_ASSERT(ssd->refCount() == 1); AutoLockForExclusiveAccess lock(cx); ScriptBytecodeHasher::Lookup l(ssd); ScriptDataTable::AddPtr p = cx->scriptDataTable(lock).lookupForAdd(l); if (p) { - js_free(ssd); - ssd = *p; + MOZ_ASSERT(ssd != *p); + freeScriptData(); + setScriptData(*p); } else { if (!cx->scriptDataTable(lock).add(p, ssd)) { - script->setCode(nullptr); - script->atoms = nullptr; - js_free(ssd); + freeScriptData(); ReportOutOfMemory(cx); return false; } + + // Being in the table counts as a reference on the script data. + scriptData()->incRefCount(); } - /* - * During the IGC we need to ensure that bytecode is marked whenever it is - * accessed even if the bytecode was already in the table: at this point - * old scripts or exceptions pointing to the bytecode may no longer be - * reachable. This is effectively a read barrier. - */ - if (cx->isJSContext()) { - JSContext* ncx = cx->asJSContext(); - if (JS::IsIncrementalGCInProgress(ncx) && ncx->gc.isFullGc()) - ssd->marked = true; - } - - script->setCode(ssd->data); - script->atoms = ssd->atoms(); + MOZ_ASSERT(scriptData()->refCount() >= 2); return true; } -static inline void -MarkScriptData(JSRuntime* rt, const jsbytecode* bytecode) -{ - /* - * As an invariant, a ScriptBytecodeEntry should not be 'marked' outside of - * a GC. Since SweepScriptBytecodes is only called during a full gc, - * to preserve this invariant, only mark during a full gc. - */ - if (rt->gc.isFullGc()) - SharedScriptData::fromBytecode(bytecode)->marked = true; -} - -void -js::UnmarkScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock) -{ - MOZ_ASSERT(rt->gc.isFullGc()); - ScriptDataTable& table = rt->scriptDataTable(lock); - for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) { - SharedScriptData* entry = e.front(); - entry->marked = false; - } -} - void js::SweepScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock) { - MOZ_ASSERT(rt->gc.isFullGc()); + // Entries are removed from the table when their reference count is one, + // i.e. when the only reference to them is from the table entry. + ScriptDataTable& table = rt->scriptDataTable(lock); - if (rt->keepAtoms()) - return; - for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) { - SharedScriptData* entry = e.front(); - if (!entry->marked) { - js_free(entry); + SharedScriptData* scriptData = e.front(); + if (scriptData->refCount() == 1) { + scriptData->decRefCount(); e.removeFront(); } } } void js::FreeScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock) { ScriptDataTable& table = rt->scriptDataTable(lock); if (!table.initialized()) return; - for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) + for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) { +#ifdef DEBUG + SharedScriptData* scriptData = e.front(); + fprintf(stderr, "ERROR: GC found live SharedScriptData %p with ref count %d at shutdown\n", + scriptData, scriptData->refCount()); +#endif js_free(e.front()); + } table.clear(); } /* * JSScript::data and SharedScriptData::data have complex, * manually-controlled, memory layouts. * @@ -2828,24 +2811,23 @@ JSScript::partiallyInit(ExclusiveContext JSScript::fullyInitTrivial(ExclusiveContext* cx, Handle<JSScript*> script) { if (!Bindings::initTrivialForScript(cx, script)) return false; if (!partiallyInit(cx, script, 0, 0, 0, 0, 0, 0)) return false; - SharedScriptData* ssd = SharedScriptData::new_(cx, 1, 1, 0); - if (!ssd) + if (!script->createScriptData(cx, 1, 1, 0)) return false; - ssd->data[0] = JSOP_RETRVAL; - ssd->data[1] = SRC_NULL; - script->setLength(1); - return SaveSharedScriptData(cx, script, ssd, 1); + jsbytecode* code = script->code(); + code[0] = JSOP_RETRVAL; + code[1] = SRC_NULL; + return script->shareScriptData(cx); } /* static */ void JSScript::linkToFunctionFromEmitter(js::ExclusiveContext* cx, JS::Handle<JSScript*> script, js::frontend::FunctionBox* funbox) { script->funHasExtensibleScope_ = funbox->hasExtensibleScope(); script->funNeedsDeclEnvObject_ = funbox->needsDeclEnvObject(); @@ -2921,29 +2903,26 @@ JSScript::fullyInitFromEmitter(Exclusive return false; } MOZ_ASSERT(script->mainOffset() == 0); script->mainOffset_ = prologueLength; script->lineno_ = bce->firstLine; - script->setLength(prologueLength + mainLength); - script->natoms_ = natoms; - SharedScriptData* ssd = SharedScriptData::new_(cx, script->length(), nsrcnotes, natoms); - if (!ssd) + if (!script->createScriptData(cx, prologueLength + mainLength, nsrcnotes, natoms)) return false; - jsbytecode* code = ssd->data; + jsbytecode* code = script->code(); PodCopy<jsbytecode>(code, bce->prologue.code.begin(), prologueLength); PodCopy<jsbytecode>(code + prologueLength, bce->main.code.begin(), mainLength); bce->copySrcNotes((jssrcnote*)(code + script->length()), nsrcnotes); - InitAtomMap(bce->atomIndices.getMap(), ssd->atoms()); - - if (!SaveSharedScriptData(cx, script, ssd, nsrcnotes)) + InitAtomMap(bce->atomIndices.getMap(), script->atoms()); + + if (!script->shareScriptData(cx)) return false; if (bce->constList.length() != 0) bce->constList.finish(script->consts()); if (bce->objectList.length != 0) bce->objectList.finish(script->objects()); if (bce->tryNoteList.length() != 0) bce->tryNoteList.finish(script->trynotes()); @@ -3147,16 +3126,19 @@ JSScript::finalize(FreeOp* fop) destroyScriptCounts(fop); destroyDebugScript(fop); if (data) { JS_POISON(data, 0xdb, computedSizeOfData()); fop->free_(data); } + if (scriptData_) + scriptData_->decRefCount(); + fop->runtime()->contextFromMainThread()->caches.lazyScriptCache.remove(this); // In most cases, our LazyScript's script pointer will reference this // script, and thus be nulled out by normal weakref processing. However, if // we unlazified the LazyScript during incremental sweeping, it will have a // completely different JSScript. MOZ_ASSERT_IF(lazyScript && !IsAboutToBeFinalizedUnbarriered(&lazyScript), !lazyScript->hasScript() || lazyScript->maybeScriptUnbarriered() != this); @@ -3557,23 +3539,20 @@ js::detail::CopyScript(JSContext* cx, Ha /* This assignment must occur before all the Rebase calls. */ dst->data = data.forget(); dst->dataSize_ = size; MOZ_ASSERT(bool(dst->data) == bool(src->data)); if (dst->data) memcpy(dst->data, src->data, size); /* Script filenames, bytecodes and atoms are runtime-wide. */ - dst->setCode(src->code()); - dst->atoms = src->atoms; - - dst->setLength(src->length()); + dst->setScriptData(src->scriptData()); + dst->lineno_ = src->lineno(); dst->mainOffset_ = src->mainOffset(); - dst->natoms_ = src->natoms(); dst->funLength_ = src->funLength(); dst->nTypeSets_ = src->nTypeSets(); dst->nslots_ = src->nslots(); if (src->argumentsHasVarBinding()) { dst->setArgumentsHasVarBinding(); if (src->analyzedArgsUsage()) dst->setNeedsArgsObj(src->needsArgsObj()); } @@ -3910,31 +3889,37 @@ JSScript::hasBreakpointsAt(jsbytecode* p BreakpointSite* site = getBreakpointSite(pc); if (!site) return false; return site->enabledCount > 0; } void +SharedScriptData::traceChildren(JSTracer* trc) +{ + MOZ_ASSERT(refCount() != 0); + for (uint32_t i = 0; i < natoms(); ++i) + TraceNullableEdge(trc, &atoms()[i], "atom"); +} + +void JSScript::traceChildren(JSTracer* trc) { // NOTE: this JSScript may be partially initialized at this point. E.g. we // may have created it and partially initialized it with // JSScript::Create(), but not yet finished initializing it with // fullyInitFromEmitter() or fullyInitTrivial(). MOZ_ASSERT_IF(trc->isMarkingTracer() && static_cast<GCMarker*>(trc)->shouldCheckCompartments(), zone()->isCollecting()); - if (atoms) { - for (uint32_t i = 0; i < natoms(); ++i) - TraceNullableEdge(trc, &atoms[i], "atom"); - } + if (scriptData()) + scriptData()->traceChildren(trc); if (hasObjects()) { ObjectArray* objarray = objects(); TraceRange(trc, objarray->length, objarray->vector, "objects"); } if (hasConsts()) { ConstArray* constarray = consts(); @@ -3947,23 +3932,19 @@ JSScript::traceChildren(JSTracer* trc) TraceNullableEdge(trc, &function_, "function"); TraceNullableEdge(trc, &module_, "module"); TraceNullableEdge(trc, &enclosingStaticScope_, "enclosingStaticScope"); if (maybeLazyScript()) TraceManuallyBarrieredEdge(trc, &lazyScript, "lazyScript"); - if (trc->isMarkingTracer()) { + if (trc->isMarkingTracer()) compartment()->mark(); - if (code()) - MarkScriptData(trc->runtime(), code()); - } - bindings.trace(trc); jit::TraceJitScripts(trc, this); } void LazyScript::finalize(FreeOp* fop) {
--- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -929,16 +929,106 @@ XDRLazyScript(XDRState<mode>* xdr, Handl /* * Code any constant value. */ template<XDRMode mode> bool XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp); +/* + * Common data that can be shared between many scripts in a single runtime. + */ +class SharedScriptData +{ + // This class is reference counted as follows: each pointer from a JSScript + // counts as one reference plus there may be one reference from the shared + // script data table. + mozilla::Atomic<uint32_t> refCount_; + + uint32_t dataLength_; + uint32_t natoms_; + uint32_t codeLength_; + uintptr_t data_[1]; + + public: + static SharedScriptData* new_(ExclusiveContext* cx, uint32_t codeLength, + uint32_t srcnotesLength, uint32_t natoms); + + uint32_t refCount() const { + return refCount_; + } + void incRefCount() { + refCount_++; + } + void decRefCount() { + MOZ_ASSERT(refCount_ != 0); + refCount_--; + if (refCount_ == 0) + js_free(this); + } + + uint32_t dataLength() const { + return dataLength_; + } + uint8_t* data() { + return reinterpret_cast<uint8_t*>(data_); + } + + uint32_t natoms() const { + return natoms_; + } + GCPtrAtom* atoms() { + if (!natoms_) + return nullptr; + return reinterpret_cast<GCPtrAtom*>(data()); + } + + uint32_t codeLength() const { + return codeLength_; + } + jsbytecode* code() { + return reinterpret_cast<jsbytecode*>(data() + natoms_ * sizeof(GCPtrAtom)); + } + + void traceChildren(JSTracer* trc); + + private: + SharedScriptData() = delete; + SharedScriptData(const SharedScriptData&) = delete; + SharedScriptData& operator=(const SharedScriptData&) = delete; +}; + +struct ScriptBytecodeHasher +{ + struct Lookup + { + const uint8_t* data; + uint32_t length; + + explicit Lookup(SharedScriptData* ssd) : data(ssd->data()), length(ssd->dataLength()) {} + }; + static HashNumber hash(const Lookup& l) { return mozilla::HashBytes(l.data, l.length); } + static bool match(SharedScriptData* entry, const Lookup& lookup) { + if (entry->dataLength() != lookup.length) + return false; + return mozilla::PodEqual<uint8_t>(entry->data(), lookup.data, lookup.length); + } +}; + +typedef HashSet<SharedScriptData*, + ScriptBytecodeHasher, + SystemAllocPolicy> ScriptDataTable; + +extern void +SweepScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); + +extern void +FreeScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); + } /* namespace js */ class JSScript : public js::gc::TenuredCell { template <js::XDRMode mode> friend bool js::XDRScript(js::XDRState<mode>* xdr, js::HandleObject enclosingScope, @@ -975,23 +1065,21 @@ class JSScript : public js::gc::TenuredC js::Shape* callObjShape() const { return bindings.callObjShape(); } // Word-sized fields. private: - jsbytecode* code_; /* bytecodes and their immediate operands */ + js::SharedScriptData* scriptData_; public: uint8_t* data; /* pointer to variable-length data array (see comment above Create() for details) */ - js::GCPtrAtom* atoms; /* maps immediate index to literal struct */ - JSCompartment* compartment_; private: /* Persistent type information retained across GCs. */ js::TypeScript* types_; // This script's ScriptSourceObject, or a CCW thereof. // @@ -1021,26 +1109,24 @@ class JSScript : public js::gc::TenuredC * Pointer to either baseline->method()->raw() or ion->method()->raw(), or * nullptr if there's no Baseline or Ion script. */ uint8_t* baselineOrIonRaw; uint8_t* baselineOrIonSkipArgCheck; // 32-bit fields. - uint32_t length_; /* length of code vector */ uint32_t dataSize_; /* size of the used part of the data array */ uint32_t lineno_; /* base line number of script */ uint32_t column_; /* base column of script, optionally set */ uint32_t mainOffset_;/* offset of main entry point from code, after predef'ing prologue */ - uint32_t natoms_; /* length of atoms array */ uint32_t nslots_; /* vars plus maximum stack depth */ /* Range of characters in scriptSource which contains this script's source. */ uint32_t sourceStart_; uint32_t sourceEnd_; // Number of times the script has been called or has had backedges taken. // When running in ion, also increased for any inlined scripts. Reset if @@ -1195,17 +1281,17 @@ class JSScript : public js::gc::TenuredC bool isDerivedClassConstructor_:1; bool isDefaultClassConstructor_:1; // Add padding so JSScript is gc::Cell aligned. Make padding protected // instead of private to suppress -Wunused-private-field compiler warnings. protected: #if JS_BITS_PER_WORD == 32 - // No padding currently required. + uint32_t padding_; #endif // // End of fields. Start methods. // public: static JSScript* Create(js::ExclusiveContext* cx, @@ -1243,27 +1329,31 @@ class JSScript : public js::gc::TenuredC public: inline JSPrincipals* principals(); JSCompartment* compartment() const { return compartment_; } JSCompartment* maybeCompartment() const { return compartment(); } void setVersion(JSVersion v) { version = v; } + js::SharedScriptData* scriptData() { + return scriptData_; + } + // Script bytecode is immutable after creation. jsbytecode* code() const { - return code_; + if (!scriptData_) + return nullptr; + return scriptData_->code(); } size_t length() const { - return length_; + MOZ_ASSERT(scriptData_); + return scriptData_->codeLength(); } - void setCode(jsbytecode* code) { code_ = code; } - void setLength(size_t length) { length_ = length; } - jsbytecode* codeEnd() const { return code() + length(); } jsbytecode* lastPC() const { jsbytecode* pc = codeEnd() - js::JSOP_RETRVAL_LENGTH; MOZ_ASSERT(*pc == JSOP_RETRVAL); return pc; } @@ -1709,16 +1799,22 @@ class JSScript : public js::gc::TenuredC // Switch the script over from the off-thread compartment's static // global lexical scope to the main thread compartment's. void fixEnclosingStaticGlobalLexicalScope(); private: bool makeTypes(JSContext* cx); + bool createScriptData(js::ExclusiveContext* cx, uint32_t codeLength, uint32_t srcnotesLength, + uint32_t natoms); + bool shareScriptData(js::ExclusiveContext* cx); + void freeScriptData(); + void setScriptData(js::SharedScriptData* data); + public: uint32_t getWarmUpCount() const { return warmUpCount; } uint32_t incWarmUpCounter(uint32_t amount = 1) { return warmUpCount += amount; } uint32_t* addressOfWarmUpCounter() { return reinterpret_cast<uint32_t*>(&warmUpCount); } static size_t offsetOfWarmUpCounter() { return offsetof(JSScript, warmUpCount); } void resetWarmUpCounter() { incWarmUpResetCounter(); warmUpCount = 0; } uint16_t getWarmUpResetCount() const { return warmUpResetCount; } @@ -1802,21 +1898,28 @@ class JSScript : public js::gc::TenuredC js::YieldOffsetArray& yieldOffsets() { MOZ_ASSERT(hasYieldOffsets()); return *reinterpret_cast<js::YieldOffsetArray*>(data + yieldOffsetsOffset()); } bool hasLoops(); - size_t natoms() const { return natoms_; } + size_t natoms() const { + MOZ_ASSERT(scriptData_); + return scriptData_->natoms(); + } + js::GCPtrAtom* atoms() const { + MOZ_ASSERT(scriptData_); + return scriptData_->atoms(); + } js::GCPtrAtom& getAtom(size_t index) const { MOZ_ASSERT(index < natoms()); - return atoms[index]; + return atoms()[index]; } js::GCPtrAtom& getAtom(jsbytecode* pc) const { MOZ_ASSERT(containsPC(pc) && containsPC(pc + sizeof(uint32_t))); return getAtom(GET_UINT32_INDEX(pc)); } js::PropertyName* getName(size_t index) { @@ -2406,71 +2509,16 @@ class LazyScript : public gc::TenuredCel uint64_t packedFields() const { return packedFields_; } }; /* If this fails, add/remove padding within LazyScript. */ JS_STATIC_ASSERT(sizeof(LazyScript) % js::gc::CellSize == 0); -struct SharedScriptData -{ - uint32_t length; - uint32_t natoms; - mozilla::Atomic<bool, mozilla::ReleaseAcquire> marked; - jsbytecode data[1]; - - static SharedScriptData* new_(ExclusiveContext* cx, uint32_t codeLength, - uint32_t srcnotesLength, uint32_t natoms); - - GCPtrAtom* atoms() { - if (!natoms) - return nullptr; - return reinterpret_cast<GCPtrAtom*>(data + length - sizeof(JSAtom*) * natoms); - } - - static SharedScriptData* fromBytecode(const jsbytecode* bytecode) { - return (SharedScriptData*)(bytecode - offsetof(SharedScriptData, data)); - } - - private: - SharedScriptData() = delete; - SharedScriptData(const SharedScriptData&) = delete; -}; - -struct ScriptBytecodeHasher -{ - struct Lookup - { - jsbytecode* code; - uint32_t length; - - explicit Lookup(SharedScriptData* ssd) : code(ssd->data), length(ssd->length) {} - }; - static HashNumber hash(const Lookup& l) { return mozilla::HashBytes(l.code, l.length); } - static bool match(SharedScriptData* entry, const Lookup& lookup) { - if (entry->length != lookup.length) - return false; - return mozilla::PodEqual<jsbytecode>(entry->data, lookup.code, lookup.length); - } -}; - -typedef HashSet<SharedScriptData*, - ScriptBytecodeHasher, - SystemAllocPolicy> ScriptDataTable; - -extern void -UnmarkScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); - -extern void -SweepScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); - -extern void -FreeScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); - struct ScriptAndCounts { /* This structure is stored and marked from the JSRuntime. */ JSScript* script; ScriptCounts scriptCounts; inline explicit ScriptAndCounts(JSScript* script); inline ScriptAndCounts(ScriptAndCounts&& sac);
--- a/js/src/tests/browser.js +++ b/js/src/tests/browser.js @@ -8,48 +8,147 @@ // nested shell.js/browser.js. Second, can you instead add it to // shell.js? Our goal is to unify these two files for readability, and // the plan is to empty out this file into that one over time. Third, // supposing you must add to this file, please add it to this IIFE for // better modularity/resilience against tests that must do particularly // bizarre things that might break the harness. (function(global) { + /********************************************************************** + * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) * + **********************************************************************/ + + var ReflectApply = global.Reflect.apply; + + // BEWARE: ObjectGetOwnPropertyDescriptor is only safe to use if its result + // is inspected using own-property-examining functionality. Directly + // accessing properties on a returned descriptor without first + // verifying the property's existence can invoke user-modifiable + // behavior. + var ObjectGetOwnPropertyDescriptor = global.Object.getOwnPropertyDescriptor; + + var document = global.document; + var documentBody = global.document.body; + var documentDocumentElement = global.document.documentElement; + var DocumentCreateElement = global.document.createElement; + var ElementInnerHTMLSetter = + ObjectGetOwnPropertyDescriptor(global.Element.prototype, "innerHTML").set; + var HTMLIFramePrototypeContentWindowGetter = + ObjectGetOwnPropertyDescriptor(global.HTMLIFrameElement.prototype, "contentWindow").get; + var HTMLIFramePrototypeRemove = global.HTMLIFrameElement.prototype.remove; + var NodePrototypeAppendChild = global.Node.prototype.appendChild; + var NodePrototypeTextContentSetter = + ObjectGetOwnPropertyDescriptor(global.Node.prototype, "textContent").set; + + // Cached DOM nodes used by the test harness itself. (We assume the test + // doesn't misbehave in a way that actively interferes with what the test + // harness runner observes, e.g. navigating the page to a different location. + // Short of running every test in a worker -- which has its own problems -- + // there's no way to isolate a test from the page to that extent.) + var printOutputContainer = + global.document.getElementById("jsreftest-print-output-container"); + + /**************************** + * GENERAL HELPER FUNCTIONS * + ****************************/ + + function AppendChild(elt, kid) { + ReflectApply(NodePrototypeAppendChild, elt, [kid]); + } + + function CreateElement(name) { + return ReflectApply(DocumentCreateElement, document, [name]); + } + + function HTMLSetAttribute(element, name, value) { + ReflectApply(HTMLElementPrototypeSetAttribute, element, [name, value]); + } + + function SetTextContent(element, text) { + ReflectApply(NodePrototypeTextContentSetter, element, [text]); + } + /**************************** * UTILITY FUNCTION EXPORTS * ****************************/ var newGlobal = global.newGlobal; if (typeof newGlobal !== "function") { newGlobal = function newGlobal() { - var iframe = global.document.createElement("iframe"); - global.document.documentElement.appendChild(iframe); - var win = iframe.contentWindow; - iframe.remove(); + var iframe = CreateElement("iframe"); + AppendChild(documentDocumentElement, iframe); + var win = + ReflectApply(HTMLIFramePrototypeContentWindowGetter, iframe, []); + ReflectApply(HTMLIFramePrototypeRemove, iframe, []); + // Shim in "evaluate" win.evaluate = win.eval; return win; }; global.newGlobal = newGlobal; } - // This function is *only* used in this file! Ultimately it should only be - // used by other exports in this IIFE, but for now just export it so that - // functions not exported within this IIFE (but still in this file) can use - // it. - function DocumentWrite(s) { - try { - var msgDiv = global.document.createElement('div'); - msgDiv.innerHTML = s; - global.document.body.appendChild(msgDiv); - } catch (e) { - global.document.write(s + '<br>\n'); + // This function is *only* used by shell.js's for-browsers |print()| function! + // It's only defined/exported here because it needs CreateElement and friends, + // only defined here, and we're not yet ready to move them to shell.js. + function AddPrintOutput(s) { + var msgDiv = CreateElement("div"); + SetTextContent(msgDiv, s); + AppendChild(printOutputContainer, msgDiv); + } + global.AddPrintOutput = AddPrintOutput; + + /************************************************************************* + * HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) * + *************************************************************************/ + + // This overwrites shell.js's version that merely prints the given string. + function writeHeaderToLog(string) { + string = String(string); + + // First dump to the console. + dump(string + "\n"); + + // Then output to the page. + var h2 = CreateElement("h2"); + SetTextContent(h2, string); + AppendChild(printOutputContainer, h2); + } + global.writeHeaderToLog = writeHeaderToLog; + + // XXX This function overwrites one in shell.js. We should define the + // separate versions in a single location. Also the dependence on + // |global.{PASSED,FAILED}| is very silly. + function writeFormattedResult(expect, actual, string, passed) { + // XXX remove this? it's unneeded in the shell version + string = String(string); + + dump(string + "\n"); + + var font = CreateElement("font"); + if (passed) { + HTMLSetAttribute(font, "color", "#009900"); + SetTextContent(font, " \u00A0" + global.PASSED); + } else { + HTMLSetAttribute(font, "color", "#aa0000"); + SetTextContent(font, "\u00A0" + global.FAILED + expect); } + + var b = CreateElement("b"); + AppendChild(b, font); + + var tt = CreateElement("tt"); + SetTextContent(tt, string); + AppendChild(tt, b); + + AppendChild(printOutputContainer, tt); + AppendChild(printOutputContainer, CreateElement("br")); } - global.DocumentWrite = DocumentWrite; + global.writeFormattedResult = writeFormattedResult; })(this); var gPageCompleted; var GLOBAL = this + ''; // Variables local to jstests harness. var jstestsTestPassesUnlessItThrows = false; @@ -62,93 +161,24 @@ var jstestsOptions; * * Overrides the same-named function in shell.js. */ function testPassesUnlessItThrows() { jstestsTestPassesUnlessItThrows = true; } /* - * Requests to load the given JavaScript file before the file containing the - * test case. - */ -function include(file) { - outputscripttag(file, {language: "type", mimetype: "text/javascript"}); -} - -/* * Sets a restore function which restores the standard built-in ECMAScript * properties after a destructive test case, and which will be called after * the test case terminates. */ function setRestoreFunction(restore) { jstestsRestoreFunction = restore; } -function htmlesc(str) { - if (str == '<') - return '<'; - if (str == '>') - return '>'; - if (str == '&') - return '&'; - return str; -} - -function print() { - var s = 'TEST-INFO | '; - var a; - for (var i = 0; i < arguments.length; i++) - { - a = arguments[i]; - s += String(a) + ' '; - } - - if (typeof dump == 'function') - { - dump( s + '\n'); - } - - s = s.replace(/[<>&]/g, htmlesc); - - DocumentWrite(s); -} - -function writeHeaderToLog( string ) { - string = String(string); - - if (typeof dump == 'function') - { - dump( string + '\n'); - } - - string = string.replace(/[<>&]/g, htmlesc); - - DocumentWrite( "<h2>" + string + "</h2>" ); -} - -function writeFormattedResult( expect, actual, string, passed ) { - string = String(string); - - if (typeof dump == 'function') - { - dump( string + '\n'); - } - - string = string.replace(/[<>&]/g, htmlesc); - - var s = "<tt>"+ string ; - s += "<b>" ; - s += ( passed ) ? "<font color=#009900> " + PASSED - : "<font color=#aa0000> " + FAILED + expect; - - DocumentWrite( s + "</font></b></tt><br>" ); - return passed; -} - window.onerror = function (msg, page, line) { jstestsTestPassesUnlessItThrows = false; // Restore options in case a test case used this common variable name. options = jstestsOptions; // Restore the ECMAScript environment after potentially destructive tests. @@ -189,20 +219,16 @@ function gc() SpecialPowers.forceGC(); } catch(ex) { print('gc: ' + ex); } } -function quit() -{ -} - function options(aOptionName) { // return value of options() is a comma delimited list // of the previously set values var value = ''; for (var optionName in options.currvalues) { @@ -271,21 +297,16 @@ function optionsInit() { } else { options.initvalues[optionName] = true; } } } -function gczeal(z) -{ - SpecialPowers.setGCZeal(z); -} - function jsTestDriverBrowserInit() { if (typeof dump != 'function') { dump = print; }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-1.js +++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-1.js @@ -63,15 +63,14 @@ function test() { gTestcases[gTc].passed = writeTestCaseResult( gTestcases[gTc].expect, gTestcases[gTc].actual, gTestcases[gTc].description +" = "+ gTestcases[gTc].actual ); gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value "; } - stopTest(); return ( gTestcases ); } function MyObject() { this.eval = new Function( "x", "return(Math.pow(Number(x),2))" ); }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-10.js +++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-10.js @@ -61,11 +61,10 @@ function test() { gTestcases[gTc].passed = writeTestCaseResult( gTestcases[gTc].expect, gTestcases[gTc].actual, gTestcases[gTc].description +" = "+ gTestcases[gTc].actual ); gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value "; } - stopTest(); return ( gTestcases ); }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-2.js +++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-2.js @@ -65,15 +65,14 @@ function test() { gTestcases[gTc].passed = writeTestCaseResult( gTestcases[gTc].expect, gTestcases[gTc].actual, gTestcases[gTc].description +" = "+ gTestcases[gTc].actual ); gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value "; } - stopTest(); return ( gTestcases ); } function MyObject() { this.eval = new Function( "x", "return(Math.pow(Number(x),2))" ); }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-3.js +++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-3.js @@ -63,15 +63,14 @@ function test() { gTestcases[gTc].passed = writeTestCaseResult( gTestcases[gTc].expect, gTestcases[gTc].actual, gTestcases[gTc].description +" = "+ gTestcases[gTc].actual ); gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value "; } - stopTest(); return ( gTestcases ); } function MyObject() { this.eval = new Function( "x", "return(Math.pow(Number(x),2))" ); }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-4.js +++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-4.js @@ -65,15 +65,14 @@ function test() { gTestcases[gTc].passed = writeTestCaseResult( gTestcases[gTc].expect, gTestcases[gTc].actual, gTestcases[gTc].description +" = "+ gTestcases[gTc].actual ); gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value "; } - stopTest(); return ( gTestcases ); } function MyObject() { this.eval = new Function( "x", "return(Math.pow(Number(x),2))" ); }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-5.js +++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-5.js @@ -65,14 +65,13 @@ function test() { gTestcases[gTc].passed = writeTestCaseResult( gTestcases[gTc].expect, gTestcases[gTc].actual, gTestcases[gTc].description +" = "+ gTestcases[gTc].actual ); gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value "; } - stopTest(); return ( gTestcases ); } function MyObject() { this.eval = new Function( "x", "return(Math.pow(Number(x),2))" ); }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-7.js +++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-7.js @@ -64,15 +64,14 @@ function test() { gTestcases[gTc].passed = writeTestCaseResult( gTestcases[gTc].expect, gTestcases[gTc].actual, gTestcases[gTc].description +" = "+ gTestcases[gTc].actual ); gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value "; } - stopTest(); return ( gTestcases ); } function MyObject() { this.eval = new Function( "x", "return(Math.pow(Number(x),2))" ); }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.4-8.js +++ b/js/src/tests/ecma/ExecutionContexts/10.1.4-8.js @@ -65,15 +65,14 @@ function test() { gTestcases[gTc].passed = writeTestCaseResult( gTestcases[gTc].expect, gTestcases[gTc].actual, gTestcases[gTc].description +" = "+ gTestcases[gTc].actual ); gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value "; } - stopTest(); return ( gTestcases ); } function MyObject() { this.eval = new Function( "x", "return(Math.pow(Number(x),2))" ); }
--- a/js/src/tests/ecma/ExecutionContexts/10.1.5-3.js +++ b/js/src/tests/ecma/ExecutionContexts/10.1.5-3.js @@ -86,11 +86,10 @@ function test() { gTestcases[gTc].passed = writeTestCaseResult( gTestcases[gTc].expect, gTestcases[gTc].actual, gTestcases[gTc].description +" = "+ gTestcases[gTc].actual ); gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value "; } - stopTest(); return ( gTestcases ); }
--- a/js/src/tests/ecma/LexicalConventions/7.3-1.js +++ b/js/src/tests/ecma/LexicalConventions/7.3-1.js @@ -48,11 +48,10 @@ function test() { gTestcases[gTc].passed = writeTestCaseResult( gTestcases[gTc].expect, gTestcases[gTc].actual, gTestcases[gTc].description +": "+ gTestcases[gTc].actual ); gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : " ignored chars after line terminator of single-line comment"; } - stopTest(); return ( gTestcases ); }
--- a/js/src/tests/ecma/extensions/10.1.4-9.js +++ b/js/src/tests/ecma/extensions/10.1.4-9.js @@ -63,14 +63,13 @@ function test() { gTestcases[gTc].passed = writeTestCaseResult( gTestcases[gTc].expect, gTestcases[gTc].actual, gTestcases[gTc].description +" = "+ gTestcases[gTc].actual ); gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value "; } - stopTest(); return ( gTestcases ); } function MyObject( n ) { this.__proto__ = Number.prototype; }
--- a/js/src/tests/jsreftest.html +++ b/js/src/tests/jsreftest.html @@ -4,16 +4,20 @@ <!-- This test driver is to be invoked using jsreftest.html?test=path-to-test-js It will load the associated test javascript file using the default script language attributes, then execute the test. --> - <script type="text/javascript" src="shell.js"> - </script> - <script type="text/javascript" src="browser.js"> - </script> </head> <body> + <!-- + print() appends div-element children to this, so this div must appear + before all script elements. + --> + <div id="jsreftest-print-output-container"></div> + + <script type="text/javascript" src="shell.js"></script> + <script type="text/javascript" src="browser.js"></script> </body> </html>
--- a/js/src/tests/shell.js +++ b/js/src/tests/shell.js @@ -13,24 +13,36 @@ /********************************************************************** * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) * **********************************************************************/ var undefined; // sigh var Error = global.Error; var Number = global.Number; + var String = global.String; var TypeError = global.TypeError; var ArrayIsArray = global.Array.isArray; var ObjectCreate = global.Object.create; var ObjectDefineProperty = global.Object.defineProperty; var ReflectApply = global.Reflect.apply; var StringPrototypeEndsWith = global.String.prototype.endsWith; + var runningInBrowser = typeof global.window !== "undefined"; + if (runningInBrowser) { + // Certain cached functionality only exists (and is only needed) when + // running in the browser. Segregate that caching here. + + var SpecialPowersSetGCZeal = + global.SpecialPowers ? global.SpecialPowers.setGCZeal : undefined; + } + + var runningInShell = typeof window === "undefined"; + /**************************** * GENERAL HELPER FUNCTIONS * ****************************/ // We could use Array.prototype.pop, but we don't so it's clear exactly what // dependencies this function has on test-modifiable behavior (i.e. none). function ArrayPop(arr) { assertEq(ArrayIsArray(arr), true, @@ -137,34 +149,98 @@ throw new Error(fullmsg); } global.assertThrowsInstanceOf = assertThrowsInstanceOf; /**************************** * UTILITY FUNCTION EXPORTS * ****************************/ - // Eventually this polyfill should be defined here, not in browser.js. For - // now tolerate more-resilient code depending on less-resilient code. - assertEq(typeof global.print, "function", - "print function is pre-existing, either provided by the shell or " + - "the already-executed top-level browser.js"); + var dump = global.dump; + if (typeof global.dump === "function") { + // A presumptively-functional |dump| exists, so no need to do anything. + } else { + // We don't have |dump|. Try to simulate the desired effect another way. + if (runningInBrowser) { + // We can't actually print to the console: |global.print| invokes browser + // printing functionality here (it's overwritten just below), and + // |global.dump| isn't a function that'll dump to the console (presumably + // because the preference to enable |dump| wasn't set). Just make it a + // no-op. + dump = function() {}; + } else { + // |print| prints to stdout: make |dump| do likewise. + dump = global.print; + } + global.dump = dump; + } + + var print; + if (runningInBrowser) { + // We're executing in a browser. Using |global.print| would invoke browser + // printing functionality: not what tests want! Instead, use a print + // function that syncs up with the test harness and console. + print = function print() { + var s = "TEST-INFO | "; + for (var i = 0; i < arguments.length; i++) + s += String(arguments[i]) + " "; + + // Dump the string to the console for developers and the harness. + dump(s + "\n"); + + // AddPrintOutput doesn't require HTML special characters be escaped. + global.AddPrintOutput(s); + }; + + global.print = print; + } else { + // We're executing in a shell, and |global.print| is the desired function. + print = global.print; + } + + var quit = global.quit; + if (typeof quit !== "function") { + // XXX There's something very strange about quit() in browser runs being a + // function that doesn't quit at all (!). We should get rid of quit() + // as an integral part of tests in favor of something else. + quit = function quit() {}; + global.quit = quit; + } + + var gczeal = global.gczeal; + if (typeof gczeal !== "function") { + if (typeof SpecialPowersSetGCZeal === "function") { + gczeal = function gczeal(z) { + SpecialPowersSetGCZeal(z); + }; + } else { + gczeal = function() {}; // no-op if not available + } + + global.gczeal = gczeal; + } /****************************************************** * TEST METADATA EXPORTS (these are of dubious value) * ******************************************************/ global.SECTION = ""; global.VERSION = ""; global.BUGNUMBER = ""; /************************************************************************* * HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) * *************************************************************************/ + var PASSED = " PASSED! "; + global.PASSED = PASSED; + + var FAILED = " FAILED! "; + global.FAILED = FAILED; + /** Set up test environment. */ function startTest() { if (global.BUGNUMBER) global.print("BUGNUMBER: " + global.BUGNUMBER); } global.startTest = startTest; var callStack = []; @@ -213,16 +289,24 @@ function currentFunc() { if (callStack.length == 0) return "top level script"; return callStack[callStack.length - 1]; } global.currentFunc = currentFunc; + // XXX This function is *only* used in harness functions and really shouldn't + // be exported. + var writeFormattedResult = + function writeFormattedResult(expect, actual, string, passed) { + print((passed ? PASSED : FAILED) + string + ' expected: ' + expect); + }; + global.writeFormattedResult = writeFormattedResult; + /***************************************************** * RHINO-SPECIFIC EXPORTS (are these used any more?) * *****************************************************/ function inRhino() { return typeof global.defineClass === "function"; } global.inRhino = inRhino; @@ -237,35 +321,31 @@ var cx = GetContext(); cx.setOptimizationLevel(i); } global.OptLevel = OptLevel; })(this); var STATUS = "STATUS: "; -var SECT_PREFIX = 'Section '; -var SECT_SUFFIX = ' of test - '; var gDelayTestDriverEnd = false; var gTestcases = new Array(); var gTc = gTestcases.length; var summary = ''; var description = ''; var expected = ''; var actual = ''; var msg = ''; /* * constant strings */