author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Fri, 22 Jul 2016 11:58:02 +0200 | |
changeset 306233 | e0bc88708ffed39aaab1fbc0ac461d93561195de |
parent 306163 | 81539328348ac4b64ec580c45e825353d00e96d8 (current diff) |
parent 306232 | 968c7be2c2a62daedf768fdb1c8ac316220c2895 (diff) |
child 306234 | 7c669d5d63efceb12696cd65cfa72c296013dafb |
child 306235 | 6776f3d11ac449add269876aea550a08dba9ee45 |
child 306296 | 5903f05d003ba5fff6afddc0a87666fbcc0d5a66 |
child 306353 | 30745bed2567f484ff609a6d389a0e57a1b4a8d5 |
push id | 30480 |
push user | cbook@mozilla.com |
push date | Fri, 22 Jul 2016 09:58:20 +0000 |
treeherder | mozilla-central@e0bc88708ffe [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 50.0a1 |
first release with | nightly linux32
e0bc88708ffe
/
50.0a1
/
20160722030235
/
files
nightly linux64
e0bc88708ffe
/
50.0a1
/
20160722030235
/
files
nightly mac
e0bc88708ffe
/
50.0a1
/
20160722030235
/
files
nightly win32
e0bc88708ffe
/
50.0a1
/
20160722030235
/
files
nightly win64
e0bc88708ffe
/
50.0a1
/
20160722030235
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
50.0a1
/
20160722030235
/
pushlog to previous
nightly linux64
50.0a1
/
20160722030235
/
pushlog to previous
nightly mac
50.0a1
/
20160722030235
/
pushlog to previous
nightly win32
50.0a1
/
20160722030235
/
pushlog to previous
nightly win64
50.0a1
/
20160722030235
/
pushlog to previous
|
--- a/.eslintignore +++ b/.eslintignore @@ -94,16 +94,17 @@ devtools/client/responsivedesign/** devtools/client/scratchpad/** devtools/client/shadereditor/** devtools/client/shared/*.jsm devtools/client/shared/webgl-utils.js devtools/client/shared/developer-toolbar.js devtools/client/shared/components/test/** devtools/client/shared/redux/middleware/test/** devtools/client/shared/test/** +!devtools/client/shared/test/test-actor-registry.js devtools/client/shared/widgets/*.jsm devtools/client/sourceeditor/** devtools/client/webaudioeditor/** devtools/client/webconsole/** !devtools/client/webconsole/panel.js !devtools/client/webconsole/jsterm.js !devtools/client/webconsole/console-commands.js devtools/client/webide/**
--- a/CLOBBER +++ b/CLOBBER @@ -17,9 +17,9 @@ # # Modifying this file will now automatically clobber the buildbot machines \o/ # # Are you updating CLOBBER because you think it's needed for your WebIDL # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Bug 911216 - clobber needed +Bug 1287946 - clobber due to generated SDK headers changing (bug 1182840)
--- a/accessible/base/ARIAMap.cpp +++ b/accessible/base/ARIAMap.cpp @@ -859,37 +859,73 @@ struct RoleComparator } }; } const nsRoleMapEntry* aria::GetRoleMap(dom::Element* aEl) { + return GetRoleMapFromIndex(GetRoleMapIndex(aEl)); +} + +uint8_t +aria::GetRoleMapIndex(dom::Element* aEl) +{ nsAutoString roles; if (!aEl || !aEl->GetAttr(kNameSpaceID_None, nsGkAtoms::role, roles) || roles.IsEmpty()) { // We treat role="" as if the role attribute is absent (per aria spec:8.1.1) - return nullptr; + return NO_ROLE_MAP_ENTRY_INDEX; } nsWhitespaceTokenizer tokenizer(roles); while (tokenizer.hasMoreTokens()) { // Do a binary search through table for the next role in role list const nsDependentSubstring role = tokenizer.nextToken(); size_t idx; if (BinarySearchIf(sWAIRoleMaps, 0, ArrayLength(sWAIRoleMaps), RoleComparator(role), &idx)) { - return sWAIRoleMaps + idx; + return idx; } } - // Always use some entry if there is a non-empty role string + // Always use some entry index if there is a non-empty role string // To ensure an accessible object is created - return &sLandmarkRoleMap; + return LANDMARK_ROLE_MAP_ENTRY_INDEX; +} + + +const nsRoleMapEntry* +aria::GetRoleMapFromIndex(uint8_t aRoleMapIndex) +{ + switch (aRoleMapIndex) { + case NO_ROLE_MAP_ENTRY_INDEX: + return nullptr; + case EMPTY_ROLE_MAP_ENTRY_INDEX: + return &gEmptyRoleMap; + case LANDMARK_ROLE_MAP_ENTRY_INDEX: + return &sLandmarkRoleMap; + default: + return sWAIRoleMaps + aRoleMapIndex; + } +} + +uint8_t +aria::GetIndexFromRoleMap(const nsRoleMapEntry* aRoleMapEntry) +{ + if (aRoleMapEntry == nullptr) { + return NO_ROLE_MAP_ENTRY_INDEX; + } else if (aRoleMapEntry == &gEmptyRoleMap) { + return EMPTY_ROLE_MAP_ENTRY_INDEX; + } else if (aRoleMapEntry == &sLandmarkRoleMap) { + return LANDMARK_ROLE_MAP_ENTRY_INDEX; + } else { + return aRoleMapEntry - sWAIRoleMaps; + } } uint64_t aria::UniversalStatesFor(mozilla::dom::Element* aElement) { uint64_t state = 0; uint32_t index = 0; while (MapToState(sWAIUnivStateMap[index], aElement, &state))
--- a/accessible/base/ARIAMap.h +++ b/accessible/base/ARIAMap.h @@ -198,26 +198,67 @@ namespace aria { /** * Empty role map entry. Used by accessibility service to create an accessible * if the accessible can't use role of used accessible class. For example, * it is used for table cells that aren't contained by table. */ extern nsRoleMapEntry gEmptyRoleMap; /** + * Constants for the role map entry index to indicate that the role map entry + * isn't in sWAIRoleMaps, but rather is a special entry: nullptr, + * gEmptyRoleMap, and sLandmarkRoleMap + */ +const uint8_t NO_ROLE_MAP_ENTRY_INDEX = UINT8_MAX - 2; +const uint8_t EMPTY_ROLE_MAP_ENTRY_INDEX = UINT8_MAX - 1; +const uint8_t LANDMARK_ROLE_MAP_ENTRY_INDEX = UINT8_MAX; + +/** * Get the role map entry for a given DOM node. This will use the first * ARIA role if the role attribute provides a space delimited list of roles. * - * @param aNode [in] the DOM node to get the role map entry for + * @param aEl [in] the DOM node to get the role map entry for * @return a pointer to the role map entry for the ARIA role, or nullptr * if none */ const nsRoleMapEntry* GetRoleMap(dom::Element* aEl); /** + * Get the role map entry pointer's index for a given DOM node. This will use + * the first ARIA role if the role attribute provides a space delimited list of + * roles. + * + * @param aEl [in] the DOM node to get the role map entry for + * @return the index of the pointer to the role map entry for the ARIA + * role, or NO_ROLE_MAP_ENTRY_INDEX if none + */ +uint8_t GetRoleMapIndex(dom::Element* aEl); + +/** + * Get the role map entry pointer for a given role map entry index. + * + * @param aRoleMapIndex [in] the role map index to get the role map entry + * pointer for + * @return a pointer to the role map entry for the ARIA role, + * or nullptr, if none + */ +const nsRoleMapEntry* GetRoleMapFromIndex(uint8_t aRoleMapIndex); + +/** + * Get the role map entry index for a given role map entry pointer. If the role + * map entry is within sWAIRoleMaps, return the index within that array, + * otherwise return one of the special index constants listed above. + * + * @param aRoleMap [in] the role map entry pointer to get the index for + * @return the index of the pointer to the role map entry, or + * NO_ROLE_MAP_ENTRY_INDEX if none + */ +uint8_t GetIndexFromRoleMap(const nsRoleMapEntry* aRoleMap); + +/** * Return accessible state from ARIA universal states applied to the given * element. */ uint64_t UniversalStatesFor(mozilla::dom::Element* aElement); /** * Get the ARIA attribute characteristics for a given ARIA attribute. *
--- a/accessible/generic/Accessible-inl.h +++ b/accessible/generic/Accessible-inl.h @@ -16,66 +16,91 @@ #endif namespace mozilla { namespace a11y { inline mozilla::a11y::role Accessible::Role() { - if (!mRoleMapEntry || mRoleMapEntry->roleRule != kUseMapRole) + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (!roleMapEntry || roleMapEntry->roleRule != kUseMapRole) return ARIATransformRole(NativeRole()); - return ARIATransformRole(mRoleMapEntry->role); + return ARIATransformRole(roleMapEntry->role); +} + +inline bool +Accessible::HasARIARole() const +{ + return mRoleMapEntryIndex != aria::NO_ROLE_MAP_ENTRY_INDEX; } inline bool Accessible::IsARIARole(nsIAtom* aARIARole) const { - return mRoleMapEntry && mRoleMapEntry->Is(aARIARole); + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + return roleMapEntry && roleMapEntry->Is(aARIARole); } inline bool Accessible::HasStrongARIARole() const { - return mRoleMapEntry && mRoleMapEntry->roleRule == kUseMapRole; + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + return roleMapEntry && roleMapEntry->roleRule == kUseMapRole; +} + +inline const nsRoleMapEntry* +Accessible::ARIARoleMap() const +{ + return aria::GetRoleMapFromIndex(mRoleMapEntryIndex); } inline mozilla::a11y::role Accessible::ARIARole() { - if (!mRoleMapEntry || mRoleMapEntry->roleRule != kUseMapRole) + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (!roleMapEntry || roleMapEntry->roleRule != kUseMapRole) return mozilla::a11y::roles::NOTHING; - return ARIATransformRole(mRoleMapEntry->role); + return ARIATransformRole(roleMapEntry->role); +} + +inline void +Accessible::SetRoleMapEntry(const nsRoleMapEntry* aRoleMapEntry) +{ + mRoleMapEntryIndex = aria::GetIndexFromRoleMap(aRoleMapEntry); } inline bool Accessible::IsSearchbox() const { - return (mRoleMapEntry && mRoleMapEntry->Is(nsGkAtoms::searchbox)) || + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + return (roleMapEntry && roleMapEntry->Is(nsGkAtoms::searchbox)) || (mContent->IsHTMLElement(nsGkAtoms::input) && mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::textInputType, eCaseMatters)); } inline bool Accessible::HasGenericType(AccGenericType aType) const { + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); return (mGenericTypes & aType) || - (mRoleMapEntry && mRoleMapEntry->IsOfType(aType)); + (roleMapEntry && roleMapEntry->IsOfType(aType)); } inline bool Accessible::HasNumericValue() const { if (mStateFlags & eHasNumericValue) return true; - return mRoleMapEntry && mRoleMapEntry->valueRule != eNoValue; + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + return roleMapEntry && roleMapEntry->valueRule != eNoValue; } inline void Accessible::ScrollTo(uint32_t aHow) const { if (mContent) nsCoreUtils::ScrollTo(mDoc->PresShell(), mContent, aHow); }
--- a/accessible/generic/Accessible.cpp +++ b/accessible/generic/Accessible.cpp @@ -103,18 +103,18 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(Accessible) NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(Accessible, LastRelease()) Accessible::Accessible(nsIContent* aContent, DocAccessible* aDoc) : mContent(aContent), mDoc(aDoc), mParent(nullptr), mIndexInParent(-1), - mStateFlags(0), mContextFlags(0), mType(0), mGenericTypes(0), - mRoleMapEntry(nullptr) + mRoleMapEntryIndex(aria::NO_ROLE_MAP_ENTRY_INDEX), + mStateFlags(0), mContextFlags(0), mType(0), mGenericTypes(0) { mBits.groupInfo = nullptr; mInt.mIndexOfEmbeddedChild = -1; } Accessible::~Accessible() { NS_ASSERTION(!mDoc, "LastRelease was never called!?!"); @@ -437,18 +437,19 @@ Accessible::NativeState() } // Check if a XUL element has the popup attribute (an attached popup menu). if (HasOwnContent() && mContent->IsXULElement() && mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popup)) state |= states::HASPOPUP; // Bypass the link states specialization for non links. - if (!mRoleMapEntry || mRoleMapEntry->roleRule == kUseNativeRole || - mRoleMapEntry->role == roles::LINK) + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (!roleMapEntry || roleMapEntry->roleRule == kUseNativeRole || + roleMapEntry->role == roles::LINK) state |= NativeLinkState(); return state; } uint64_t Accessible::NativeInteractiveState() const { @@ -685,17 +686,17 @@ void Accessible::SetSelected(bool aSelect) { if (!HasOwnContent()) return; Accessible* select = nsAccUtils::GetSelectableContainer(this, State()); if (select) { if (select->State() & states::MULTISELECTABLE) { - if (mRoleMapEntry) { + if (ARIARoleMap()) { if (aSelect) { mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected, NS_LITERAL_STRING("true"), true); } else { mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected, true); } } return; @@ -924,26 +925,27 @@ Accessible::Attributes() if (IsARIAHidden()) { nsAccUtils::SetAccAttr(attributes, nsGkAtoms::hidden, NS_LITERAL_STRING("true")); } // If there is no aria-live attribute then expose default value of 'live' // object attribute used for ARIA role of this accessible. - if (mRoleMapEntry) { - if (mRoleMapEntry->Is(nsGkAtoms::searchbox)) { + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (roleMapEntry) { + if (roleMapEntry->Is(nsGkAtoms::searchbox)) { nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType, NS_LITERAL_STRING("search")); } nsAutoString live; nsAccUtils::GetAccAttr(attributes, nsGkAtoms::live, live); if (live.IsEmpty()) { - if (nsAccUtils::GetLiveAttrValue(mRoleMapEntry->liveAttRule, live)) + if (nsAccUtils::GetLiveAttrValue(roleMapEntry->liveAttRule, live)) nsAccUtils::SetAccAttr(attributes, nsGkAtoms::live, live); } } return attributes.forget(); } already_AddRefed<nsIPersistentProperties> @@ -1138,23 +1140,24 @@ Accessible::State() uint64_t state = NativeState(); // Apply ARIA states to be sure accessible states will be overridden. ApplyARIAState(&state); // If this is an ARIA item of the selectable widget and if it's focused and // not marked unselected explicitly (i.e. aria-selected="false") then expose // it as selected to make ARIA widget authors life easier. - if (mRoleMapEntry && !(state & states::SELECTED) && + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (roleMapEntry && !(state & states::SELECTED) && !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_selected, nsGkAtoms::_false, eCaseMatters)) { // Special case for tabs: focused tab or focus inside related tab panel // implies selected state. - if (mRoleMapEntry->role == roles::PAGETAB) { + if (roleMapEntry->role == roles::PAGETAB) { if (state & states::FOCUSED) { state |= states::SELECTED; } else { // If focus is in a child of the tab panel surely the tab is selected! Relation rel = RelationByType(RelationType::LABEL_FOR); Accessible* relTarget = nullptr; while ((relTarget = rel.Next())) { if (relTarget->Role() == roles::PROPERTYPAGE && @@ -1217,22 +1220,23 @@ Accessible::ApplyARIAState(uint64_t* aSt if (!mContent->IsElement()) return; dom::Element* element = mContent->AsElement(); // Test for universal states first *aState |= aria::UniversalStatesFor(element); - if (mRoleMapEntry) { + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (roleMapEntry) { // We only force the readonly bit off if we have a real mapping for the aria // role. This preserves the ability for screen readers to use readonly // (primarily on the document) as the hint for creating a virtual buffer. - if (mRoleMapEntry->role != roles::NOTHING) + if (roleMapEntry->role != roles::NOTHING) *aState &= ~states::READONLY; if (mContent->HasID()) { // If has a role & ID and aria-activedescendant on the container, assume // focusable. const Accessible* ancestor = this; while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) { dom::Element* el = ancestor->Elm(); @@ -1258,31 +1262,31 @@ Accessible::ApplyARIAState(uint64_t* aSt } } // special case: A native button element whose role got transformed by ARIA to a toggle button // Also applies to togglable button menus, like in the Dev Tools Web Console. if (IsButton() || IsMenuButton()) aria::MapToState(aria::eARIAPressed, element, aState); - if (!mRoleMapEntry) + if (!roleMapEntry) return; - *aState |= mRoleMapEntry->state; - - if (aria::MapToState(mRoleMapEntry->attributeMap1, element, aState) && - aria::MapToState(mRoleMapEntry->attributeMap2, element, aState) && - aria::MapToState(mRoleMapEntry->attributeMap3, element, aState)) - aria::MapToState(mRoleMapEntry->attributeMap4, element, aState); + *aState |= roleMapEntry->state; + + if (aria::MapToState(roleMapEntry->attributeMap1, element, aState) && + aria::MapToState(roleMapEntry->attributeMap2, element, aState) && + aria::MapToState(roleMapEntry->attributeMap3, element, aState)) + aria::MapToState(roleMapEntry->attributeMap4, element, aState); // ARIA gridcell inherits editable/readonly states from the grid until it's // overridden. - if ((mRoleMapEntry->Is(nsGkAtoms::gridcell) || - mRoleMapEntry->Is(nsGkAtoms::columnheader) || - mRoleMapEntry->Is(nsGkAtoms::rowheader)) && + if ((roleMapEntry->Is(nsGkAtoms::gridcell) || + roleMapEntry->Is(nsGkAtoms::columnheader) || + roleMapEntry->Is(nsGkAtoms::rowheader)) && !(*aState & (states::READONLY | states::EDITABLE))) { const TableCellAccessible* cell = AsTableCell(); if (cell) { TableAccessible* table = cell->Table(); if (table) { Accessible* grid = table->AsAccessible(); uint64_t gridState = 0; grid->ApplyARIAState(&gridState); @@ -1290,39 +1294,40 @@ Accessible::ApplyARIAState(uint64_t* aSt } } } } void Accessible::Value(nsString& aValue) { - if (!mRoleMapEntry) + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (!roleMapEntry) return; - if (mRoleMapEntry->valueRule != eNoValue) { + if (roleMapEntry->valueRule != eNoValue) { // aria-valuenow is a number, and aria-valuetext is the optional text // equivalent. For the string value, we will try the optional text // equivalent first. if (!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext, aValue)) { mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow, aValue); } return; } // Value of textbox is a textified subtree. - if (mRoleMapEntry->Is(nsGkAtoms::textbox)) { + if (roleMapEntry->Is(nsGkAtoms::textbox)) { nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue); return; } // Value of combobox is a text of current or selected item. - if (mRoleMapEntry->Is(nsGkAtoms::combobox)) { + if (roleMapEntry->Is(nsGkAtoms::combobox)) { Accessible* option = CurrentItem(); if (!option) { uint32_t childCount = ChildCount(); for (uint32_t idx = 0; idx < childCount; idx++) { Accessible* child = mChildren.ElementAt(idx); if (child->IsListControl()) { option = child->GetSelectedItem(0); break; @@ -1357,17 +1362,18 @@ double Accessible::CurValue() const { return AttrNumericValue(nsGkAtoms::aria_valuenow); } bool Accessible::SetCurValue(double aValue) { - if (!mRoleMapEntry || mRoleMapEntry->valueRule == eNoValue) + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) return false; const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE; if (State() & kValueCannotChange) return false; double checkValue = MinValue(); if (!IsNaN(checkValue) && aValue < checkValue) @@ -1431,18 +1437,19 @@ Accessible::ARIATransformRole(role aRole } return aRole; } nsIAtom* Accessible::LandmarkRole() const { - return mRoleMapEntry && mRoleMapEntry->IsOfType(eLandmark) ? - *(mRoleMapEntry->roleAtom) : nullptr; + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + return roleMapEntry && roleMapEntry->IsOfType(eLandmark) ? + *(roleMapEntry->roleAtom) : nullptr; } role Accessible::NativeRole() { return roles::NOTHING; } @@ -1545,16 +1552,18 @@ Accessible::GetAtomicRegion() const } Relation Accessible::RelationByType(RelationType aType) { if (!HasOwnContent()) return Relation(); + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + // Relationships are defined on the same content node that the role would be // defined on. switch (aType) { case RelationType::LABELLED_BY: { Relation rel(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_labelledby)); if (mContent->IsHTMLElement()) { rel.AppendIter(new HTMLLabelIterator(Document(), this)); @@ -1596,19 +1605,19 @@ Accessible::RelationByType(RelationType return rel; } case RelationType::NODE_CHILD_OF: { Relation rel; // This is an ARIA tree or treegrid that doesn't use owns, so we need to // get the parent the hard way. - if (mRoleMapEntry && (mRoleMapEntry->role == roles::OUTLINEITEM || - mRoleMapEntry->role == roles::LISTITEM || - mRoleMapEntry->role == roles::ROW)) { + if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM || + roleMapEntry->role == roles::LISTITEM || + roleMapEntry->role == roles::ROW)) { rel.AppendTarget(GetGroupInfo()->ConceptualParent()); } // If accessible is in its own Window, or is the root of a document, // then we should provide NODE_CHILD_OF relation so that MSAA clients // can easily get to true parent instead of getting to oleacc's // ROLE_WINDOW accessible which will prevent us from going up further // (because it is system generated and has no idea about the hierarchy @@ -1624,23 +1633,23 @@ Accessible::RelationByType(RelationType } return rel; } case RelationType::NODE_PARENT_OF: { // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees // also can be organized by groups. - if (mRoleMapEntry && - (mRoleMapEntry->role == roles::OUTLINEITEM || - mRoleMapEntry->role == roles::LISTITEM || - mRoleMapEntry->role == roles::ROW || - mRoleMapEntry->role == roles::OUTLINE || - mRoleMapEntry->role == roles::LIST || - mRoleMapEntry->role == roles::TREE_TABLE)) { + if (roleMapEntry && + (roleMapEntry->role == roles::OUTLINEITEM || + roleMapEntry->role == roles::LISTITEM || + roleMapEntry->role == roles::ROW || + roleMapEntry->role == roles::OUTLINE || + roleMapEntry->role == roles::LIST || + roleMapEntry->role == roles::TREE_TABLE)) { return Relation(new ItemIterator(this)); } return Relation(); } case RelationType::CONTROLLED_BY: return Relation(new RelatedAccIterator(Document(), mContent, @@ -2438,17 +2447,18 @@ Accessible::IsWidget() const bool Accessible::IsActiveWidget() const { if (FocusMgr()->HasDOMFocus(mContent)) return true; // If text entry of combobox widget has a focus then the combobox widget is // active. - if (mRoleMapEntry && mRoleMapEntry->Is(nsGkAtoms::combobox)) { + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) { uint32_t childCount = ChildCount(); for (uint32_t idx = 0; idx < childCount; idx++) { Accessible* child = mChildren.ElementAt(idx); if (child->Role() == roles::ENTRY) return FocusMgr()->HasDOMFocus(child->GetContent()); } } @@ -2567,17 +2577,18 @@ Accessible::GetSiblingAtOffset(int32_t a *aError = NS_ERROR_UNEXPECTED; return child; } double Accessible::AttrNumericValue(nsIAtom* aAttr) const { - if (!mRoleMapEntry || mRoleMapEntry->valueRule == eNoValue) + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) return UnspecifiedNaN<double>(); nsAutoString attrValue; if (!mContent->GetAttr(kNameSpaceID_None, aAttr, attrValue)) return UnspecifiedNaN<double>(); nsresult error = NS_OK; double value = attrValue.ToDouble(&error); @@ -2597,19 +2608,20 @@ Accessible::GetActionRule() const // Has registered 'click' event handler. bool isOnclick = nsCoreUtils::HasClickListener(mContent); if (isOnclick) return eClickAction; // Get an action based on ARIA role. - if (mRoleMapEntry && - mRoleMapEntry->actionRule != eNoAction) - return mRoleMapEntry->actionRule; + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (roleMapEntry && + roleMapEntry->actionRule != eNoAction) + return roleMapEntry->actionRule; // Get an action based on ARIA attribute. if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_expanded)) return eExpandAction; return eNoAction; }
--- a/accessible/generic/Accessible.h +++ b/accessible/generic/Accessible.h @@ -225,24 +225,24 @@ public: /** * Return enumerated accessible role (see constants in Role.h). */ mozilla::a11y::role Role(); /** * Return true if ARIA role is specified on the element. */ - bool HasARIARole() const { return mRoleMapEntry; } + bool HasARIARole() const; bool IsARIARole(nsIAtom* aARIARole) const; bool HasStrongARIARole() const; /** * Retrun ARIA role map if any. */ - const nsRoleMapEntry* ARIARoleMap() const { return mRoleMapEntry; } + const nsRoleMapEntry* ARIARoleMap() const; /** * Return accessible role specified by ARIA (see constants in * roles). */ mozilla::a11y::role ARIARole(); /** @@ -378,18 +378,17 @@ public: /** * Shutdown this accessible object. */ virtual void Shutdown(); /** * Set the ARIA role map entry for a new accessible. */ - void SetRoleMapEntry(const nsRoleMapEntry* aRoleMapEntry) - { mRoleMapEntry = aRoleMapEntry; } + void SetRoleMapEntry(const nsRoleMapEntry* aRoleMapEntry); /** * Append/insert/remove a child. Return true if operation was successful. */ bool AppendChild(Accessible* aChild) { return InsertChildAt(mChildren.Length(), aChild); } virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild); @@ -1115,16 +1114,22 @@ protected: int32_t mIndexInParent; static const uint8_t kStateFlagsBits = 13; static const uint8_t kContextFlagsBits = 3; static const uint8_t kTypeBits = 6; static const uint8_t kGenericTypesBits = 16; /** + * Non-NO_ROLE_MAP_ENTRY_INDEX indicates author-supplied role; + * possibly state & value as well + */ + uint8_t mRoleMapEntryIndex; + + /** * Keep in sync with StateFlags, ContextFlags, and AccTypes. */ uint32_t mStateFlags : kStateFlagsBits; uint32_t mContextFlags : kContextFlagsBits; uint32_t mType : kTypeBits; uint32_t mGenericTypes : kGenericTypesBits; void StaticAsserts() const; @@ -1149,21 +1154,16 @@ protected: union { AccGroupInfo* groupInfo; ProxyAccessible* proxy; } mBits; friend class AccGroupInfo; - /** - * Non-null indicates author-supplied role; possibly state & value as well - */ - const nsRoleMapEntry* mRoleMapEntry; - private: Accessible() = delete; Accessible(const Accessible&) = delete; Accessible& operator =(const Accessible&) = delete; }; NS_DEFINE_STATIC_IID_ACCESSOR(Accessible,
--- a/accessible/generic/HyperTextAccessible.cpp +++ b/accessible/generic/HyperTextAccessible.cpp @@ -2212,18 +2212,19 @@ HyperTextAccessible::GetSpellTextAttr(ns if (startOffset > *aStartOffset) *aStartOffset = startOffset; } bool HyperTextAccessible::IsTextRole() { - if (mRoleMapEntry && - (mRoleMapEntry->role == roles::GRAPHIC || - mRoleMapEntry->role == roles::IMAGE_MAP || - mRoleMapEntry->role == roles::SLIDER || - mRoleMapEntry->role == roles::PROGRESSBAR || - mRoleMapEntry->role == roles::SEPARATOR)) + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (roleMapEntry && + (roleMapEntry->role == roles::GRAPHIC || + roleMapEntry->role == roles::IMAGE_MAP || + roleMapEntry->role == roles::SLIDER || + roleMapEntry->role == roles::PROGRESSBAR || + roleMapEntry->role == roles::SEPARATOR)) return false; return true; }
--- a/accessible/generic/RootAccessible.cpp +++ b/accessible/generic/RootAccessible.cpp @@ -72,17 +72,17 @@ RootAccessible::~RootAccessible() //////////////////////////////////////////////////////////////////////////////// // Accessible ENameValueFlag RootAccessible::Name(nsString& aName) { aName.Truncate(); - if (mRoleMapEntry) { + if (ARIARoleMap()) { Accessible::Name(aName); if (!aName.IsEmpty()) return eNameOK; } mDocumentNode->GetTitle(aName); return eNameOK; }
--- a/accessible/html/HTMLFormControlAccessible.cpp +++ b/accessible/html/HTMLFormControlAccessible.cpp @@ -303,17 +303,17 @@ HTMLTextFieldAccessible::NativeAttribute nsCOMPtr<nsIPersistentProperties> attributes = HyperTextAccessibleWrap::NativeAttributes(); // Expose type for text input elements as it gives some useful context, // especially for mobile. nsAutoString type; if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) { nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType, type); - if (!mRoleMapEntry && type.EqualsLiteral("search")) { + if (!ARIARoleMap() && type.EqualsLiteral("search")) { nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, NS_LITERAL_STRING("searchbox")); } } return attributes.forget(); }
--- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -592,24 +592,20 @@ nsContextMenu.prototype = { return gDevTools.showToolbox(target, "inspector").then(toolbox => { let inspector = toolbox.getCurrentPanel(); // new-node-front tells us when the node has been selected, whether the // browser is remote or not. let onNewNode = inspector.selection.once("new-node-front"); - if (this.isRemote) { - this.browser.messageManager.sendAsyncMessage("debug:inspect", {}, {node: this.target}); - inspector.walker.findInspectingNode().then(nodeFront => { - inspector.selection.setNodeFront(nodeFront, "browser-context-menu"); - }); - } else { - inspector.selection.setNode(this.target, "browser-context-menu"); - } + this.browser.messageManager.sendAsyncMessage("debug:inspect", {}, {node: this.target}); + inspector.walker.findInspectingNode().then(nodeFront => { + inspector.selection.setNodeFront(nodeFront, "browser-context-menu"); + }); return onNewNode.then(() => { // Now that the node has been selected, wait until the inspector is // fully updated. return inspector.once("inspector-updated"); }); }); },
--- a/browser/modules/BrowserUsageTelemetry.jsm +++ b/browser/modules/BrowserUsageTelemetry.jsm @@ -5,101 +5,163 @@ "use strict"; this.EXPORTED_SYMBOLS = ["BrowserUsageTelemetry"]; const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); + +// The upper bound for the count of the visited unique domain names. +const MAX_UNIQUE_VISITED_DOMAINS = 100; // Observed topic names. const WINDOWS_RESTORED_TOPIC = "sessionstore-windows-restored"; const TELEMETRY_SUBSESSIONSPLIT_TOPIC = "internal-telemetry-after-subsession-split"; const DOMWINDOW_OPENED_TOPIC = "domwindowopened"; -const DOMWINDOW_CLOSED_TOPIC = "domwindowclosed"; // Probe names. const MAX_TAB_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_tab_count"; const MAX_WINDOW_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_window_count"; const TAB_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.tab_open_event_count"; const WINDOW_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.window_open_event_count"; +const UNIQUE_DOMAINS_COUNT_SCALAR_NAME = "browser.engagement.unique_domains_count"; +const TOTAL_URI_COUNT_SCALAR_NAME = "browser.engagement.total_uri_count"; function getOpenTabsAndWinsCounts() { let tabCount = 0; let winCount = 0; let browserEnum = Services.wm.getEnumerator("navigator:browser"); while (browserEnum.hasMoreElements()) { let win = browserEnum.getNext(); winCount++; tabCount += win.gBrowser.tabs.length; } return { tabCount, winCount }; } +let URICountListener = { + // A set containing the visited domains, see bug 1271310. + _domainSet: new Set(), + + onLocationChange(browser, webProgress, request, uri, flags) { + // Don't count this URI if it's an error page. + if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) { + return; + } + + // We only care about top level loads. + if (!webProgress.isTopLevel) { + return; + } + + // Only consider http(s) schemas. + if (!uri.schemeIs("http") && !uri.schemeIs("https")) { + return; + } + + // Update the URI counts. + Services.telemetry.scalarAdd(TOTAL_URI_COUNT_SCALAR_NAME, 1); + + // We only want to count the unique domains up to MAX_UNIQUE_VISITED_DOMAINS. + if (this._domainSet.size == MAX_UNIQUE_VISITED_DOMAINS) { + return; + } + + // Unique domains should be aggregated by (eTLD + 1): x.test.com and y.test.com + // are counted once as test.com. + try { + // Even if only considering http(s) URIs, |getBaseDomain| could still throw + // due to the URI containing invalid characters or the domain actually being + // an ipv4 or ipv6 address. + this._domainSet.add(Services.eTLD.getBaseDomain(uri)); + } catch (e) { + return; + } + + Services.telemetry.scalarSet(UNIQUE_DOMAINS_COUNT_SCALAR_NAME, this._domainSet.size); + }, + + /** + * Reset the counts. This should be called when breaking a session in Telemetry. + */ + reset() { + this._domainSet.clear(); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, + Ci.nsISupportsWeakReference]), +}; + let BrowserUsageTelemetry = { init() { Services.obs.addObserver(this, WINDOWS_RESTORED_TOPIC, false); }, /** * Handle subsession splits in the parent process. */ afterSubsessionSplit() { // Scalars just got cleared due to a subsession split. We need to set the maximum // concurrent tab and window counts so that they reflect the correct value for the // new subsession. const counts = getOpenTabsAndWinsCounts(); Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount); Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount); + + // Reset the URI counter. + URICountListener.reset(); }, uninit() { Services.obs.removeObserver(this, DOMWINDOW_OPENED_TOPIC, false); - Services.obs.removeObserver(this, DOMWINDOW_CLOSED_TOPIC, false); Services.obs.removeObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false); Services.obs.removeObserver(this, WINDOWS_RESTORED_TOPIC, false); }, observe(subject, topic, data) { switch(topic) { case WINDOWS_RESTORED_TOPIC: this._setupAfterRestore(); break; case DOMWINDOW_OPENED_TOPIC: this._onWindowOpen(subject); break; - case DOMWINDOW_CLOSED_TOPIC: - this._unregisterWindow(subject); - break; case TELEMETRY_SUBSESSIONSPLIT_TOPIC: this.afterSubsessionSplit(); break; } }, handleEvent(event) { switch(event.type) { case "TabOpen": this._onTabOpen(); break; + case "unload": + this._unregisterWindow(event.target); + break; } }, /** * This gets called shortly after the SessionStore has finished restoring * windows and tabs. It counts the open tabs and adds listeners to all the * windows. */ _setupAfterRestore() { // Make sure to catch new chrome windows and subsession splits. Services.obs.addObserver(this, DOMWINDOW_OPENED_TOPIC, false); - Services.obs.addObserver(this, DOMWINDOW_CLOSED_TOPIC, false); Services.obs.addObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false); // Attach the tabopen handlers to the existing Windows. let browserEnum = Services.wm.getEnumerator("navigator:browser"); while (browserEnum.hasMoreElements()) { this._registerWindow(browserEnum.getNext()); } @@ -108,30 +170,38 @@ let BrowserUsageTelemetry = { Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount); Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount); }, /** * Adds listeners to a single chrome window. */ _registerWindow(win) { + win.addEventListener("unload", this); win.addEventListener("TabOpen", this, true); + + // Don't include URI and domain counts when in private mode. + if (PrivateBrowsingUtils.isWindowPrivate(win)) { + return; + } + win.gBrowser.addTabsProgressListener(URICountListener); }, /** * Removes listeners from a single chrome window. */ _unregisterWindow(win) { - // Ignore non-browser windows. - if (!(win instanceof Ci.nsIDOMWindow) || - win.document.documentElement.getAttribute("windowtype") != "navigator:browser") { + win.removeEventListener("unload", this); + win.removeEventListener("TabOpen", this, true); + + // Don't include URI and domain counts when in private mode. + if (PrivateBrowsingUtils.isWindowPrivate(win.defaultView)) { return; } - - win.removeEventListener("TabOpen", this, true); + win.defaultView.gBrowser.removeTabsProgressListener(URICountListener); }, /** * Updates the tab counts. * @param {Number} [newTabCount=0] The count of the opened tabs across all windows. This * is computed manually if not provided. */ _onTabOpen(tabCount = 0) {
--- a/browser/modules/test/browser_UsageTelemetry.js +++ b/browser/modules/test/browser_UsageTelemetry.js @@ -1,143 +1,268 @@ "use strict"; const MAX_CONCURRENT_TABS = "browser.engagement.max_concurrent_tab_count"; const TAB_EVENT_COUNT = "browser.engagement.tab_open_event_count"; const MAX_CONCURRENT_WINDOWS = "browser.engagement.max_concurrent_window_count"; const WINDOW_OPEN_COUNT = "browser.engagement.window_open_event_count"; +const TOTAL_URI_COUNT = "browser.engagement.total_uri_count"; +const UNIQUE_DOMAINS_COUNT = "browser.engagement.unique_domains_count"; const TELEMETRY_SUBSESSION_TOPIC = "internal-telemetry-after-subsession-split"; /** + * Waits for the web progress listener associated with this tab to fire an + * onLocationChange for a non-error page. + * + * @param {xul:browser} browser + * A xul:browser. + * + * @return {Promise} + * @resolves When navigating to a non-error page. + */ +function browserLocationChanged(browser) { + return new Promise(resolve => { + let wpl = { + onStateChange() {}, + onSecurityChange() {}, + onStatusChange() {}, + onLocationChange(aWebProgress, aRequest, aURI, aFlags) { + if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE)) { + browser.webProgress.removeProgressListener(filter); + filter.removeProgressListener(wpl); + resolve(); + } + }, + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIWebProgressListener, + Ci.nsIWebProgressListener2, + ]), + }; + const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"] + .createInstance(Ci.nsIWebProgress); + filter.addProgressListener(wpl, Ci.nsIWebProgress.NOTIFY_ALL); + browser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL); + }); +} + +/** * An helper that checks the value of a scalar if it's expected to be > 0, * otherwise makes sure that the scalar it's not reported. */ let checkScalar = (scalars, scalarName, value, msg) => { if (value > 0) { is(scalars[scalarName], value, msg); return; } ok(!(scalarName in scalars), scalarName + " must not be reported."); }; /** * Get a snapshot of the scalars and check them against the provided values. */ -let checkScalars = (maxTabs, tabOpenCount, maxWindows, windowsOpenCount) => { +let checkScalars = (maxTabs, tabOpenCount, maxWindows, windowsOpenCount, totalURIs, domainCount) => { const scalars = Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); // Check the expected values. Scalars that are never set must not be reported. checkScalar(scalars, MAX_CONCURRENT_TABS, maxTabs, "The maximum tab count must match the expected value."); checkScalar(scalars, TAB_EVENT_COUNT, tabOpenCount, "The number of open tab event count must match the expected value."); checkScalar(scalars, MAX_CONCURRENT_WINDOWS, maxWindows, "The maximum window count must match the expected value."); checkScalar(scalars, WINDOW_OPEN_COUNT, windowsOpenCount, "The number of window open event count must match the expected value."); + checkScalar(scalars, TOTAL_URI_COUNT, totalURIs, + "The total URI count must match the expected value."); + checkScalar(scalars, UNIQUE_DOMAINS_COUNT, domainCount, + "The unique domains count must match the expected value."); }; add_task(function* test_tabsAndWindows() { // Let's reset the counts. Services.telemetry.clearScalars(); let openedTabs = []; let expectedTabOpenCount = 0; let expectedWinOpenCount = 0; let expectedMaxTabs = 0; let expectedMaxWins = 0; // Add a new tab and check that the count is right. openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank")); expectedTabOpenCount = 1; expectedMaxTabs = 2; - checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount); + checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount, 0, 0); // Add two new tabs in the same window. openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank")); openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank")); expectedTabOpenCount += 2; expectedMaxTabs += 2; - checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount); + checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount, 0, 0); // Add a new window and then some tabs in it. An empty new windows counts as a tab. let win = yield BrowserTestUtils.openNewBrowserWindow(); openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank")); openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank")); openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank")); // The new window started with a new tab, so account for it. expectedTabOpenCount += 4; expectedWinOpenCount += 1; expectedMaxWins = 2; expectedMaxTabs += 4; // Remove a tab from the first window, the max shouldn't change. yield BrowserTestUtils.removeTab(openedTabs.pop()); - checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount); + checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount, 0, 0); // Remove all the extra windows and tabs. for (let tab of openedTabs) { yield BrowserTestUtils.removeTab(tab); } yield BrowserTestUtils.closeWindow(win); // Make sure all the scalars still have the expected values. - checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount); + checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount, 0, 0); }); add_task(function* test_subsessionSplit() { // Let's reset the counts. Services.telemetry.clearScalars(); // Add a new window (that will have 4 tabs). let win = yield BrowserTestUtils.openNewBrowserWindow(); let openedTabs = []; openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank")); openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank")); - openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank")); + openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://www.example.com")); // Check that the scalars have the right values. - checkScalars(5 /*maxTabs*/, 4 /*tabOpen*/, 2 /*maxWins*/, 1 /*winOpen*/); + checkScalars(5 /*maxTabs*/, 4 /*tabOpen*/, 2 /*maxWins*/, 1 /*winOpen*/, + 1 /* toalURIs */, 1 /* uniqueDomains */); // Remove a tab. yield BrowserTestUtils.removeTab(openedTabs.pop()); // Simulate a subsession split by clearing the scalars (via |snapshotScalars|) and // notifying the subsession split topic. Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true /* clearScalars*/); Services.obs.notifyObservers(null, TELEMETRY_SUBSESSION_TOPIC, ""); // After a subsession split, only the MAX_CONCURRENT_* scalars must be available // and have the correct value. No tabs or windows were opened so other scalars // must not be reported. - checkScalars(4 /*maxTabs*/, 0 /*tabOpen*/, 2 /*maxWins*/, 0 /*winOpen*/); + checkScalars(4 /*maxTabs*/, 0 /*tabOpen*/, 2 /*maxWins*/, 0 /*winOpen*/, + 0 /* toalURIs */, 0 /* uniqueDomains */); // Remove all the extra windows and tabs. for (let tab of openedTabs) { yield BrowserTestUtils.removeTab(tab); } yield BrowserTestUtils.closeWindow(win); }); +add_task(function* test_URIAndDomainCounts() { + // Let's reset the counts. + Services.telemetry.clearScalars(); + + let checkCounts = (URICount, domainCount) => { + // Get a snapshot of the scalars and then clear them. + const scalars = + Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + checkScalar(scalars, TOTAL_URI_COUNT, URICount, + "The URI scalar must contain the expected value."); + checkScalar(scalars, UNIQUE_DOMAINS_COUNT, domainCount, + "The unique domains scalar must contain the expected value."); + }; + + // Check that about:blank doesn't get counted in the URI total. + let firstTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"); + checkCounts(0, 0); + + // Open a different page and check the counts. + yield BrowserTestUtils.loadURI(firstTab.linkedBrowser, "http://example.com/"); + yield BrowserTestUtils.browserLoaded(firstTab.linkedBrowser); + checkCounts(1, 1); + + // Activating a different tab must not increase the URI count. + let secondTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"); + yield BrowserTestUtils.switchTab(gBrowser, firstTab); + checkCounts(1, 1); + yield BrowserTestUtils.removeTab(secondTab); + + // Open a new window and set the tab to a new address. + let newWin = yield BrowserTestUtils.openNewBrowserWindow(); + yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "http://example.com/"); + yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser); + checkCounts(2, 1); + + // We should not count AJAX requests. + const XHR_URL = "http://example.com/r"; + yield ContentTask.spawn(newWin.gBrowser.selectedBrowser, XHR_URL, function(url) { + return new Promise(resolve => { + var xhr = new content.window.XMLHttpRequest(); + xhr.open("GET", url); + xhr.onload = () => resolve(); + xhr.send(); + }); + }); + checkCounts(2, 1); + + // Check that we're counting page fragments. + let loadingStopped = browserLocationChanged(newWin.gBrowser.selectedBrowser); + yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "http://example.com/#2"); + yield loadingStopped; + checkCounts(3, 1); + + // Check test.domain.com and some.domain.com are only counted once unique. + yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "http://test1.example.com/"); + yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser); + checkCounts(4, 1); + + // Make sure that the unique domains counter is incrementing for a different domain. + yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "https://example.org/"); + yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser); + checkCounts(5, 2); + + // Check that we only account for top level loads (e.g. we don't count URIs from + // embedded iframes). + yield ContentTask.spawn(newWin.gBrowser.selectedBrowser, null, function* () { + let doc = content.document; + let iframe = doc.createElement("iframe"); + let promiseIframeLoaded = ContentTaskUtils.waitForEvent(iframe, "load", false); + iframe.src = "https://example.org/test"; + doc.body.insertBefore(iframe, doc.body.firstChild); + yield promiseIframeLoaded; + }); + checkCounts(5, 2); + + // Clean up. + yield BrowserTestUtils.removeTab(firstTab); + yield BrowserTestUtils.closeWindow(newWin); +}); + add_task(function* test_privateMode() { // Let's reset the counts. Services.telemetry.clearScalars(); // Open a private window and load a website in it. let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true}); yield BrowserTestUtils.loadURI(privateWin.gBrowser.selectedBrowser, "http://example.com/"); yield BrowserTestUtils.browserLoaded(privateWin.gBrowser.selectedBrowser); // Check that tab and window count is recorded. const scalars = Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN); + ok(!(TOTAL_URI_COUNT in scalars), "We should not track URIs in private mode."); + ok(!(UNIQUE_DOMAINS_COUNT in scalars), "We should not track unique domains in private mode."); is(scalars[TAB_EVENT_COUNT], 1, "The number of open tab event count must match the expected value."); is(scalars[MAX_CONCURRENT_TABS], 2, "The maximum tab count must match the expected value."); is(scalars[WINDOW_OPEN_COUNT], 1, "The number of window open event count must match the expected value."); is(scalars[MAX_CONCURRENT_WINDOWS], 2, "The maximum window count must match the expected value."); // Clean up. yield BrowserTestUtils.closeWindow(privateWin); });
--- a/build/annotationProcessors/AnnotationProcessor.java +++ b/build/annotationProcessors/AnnotationProcessor.java @@ -52,36 +52,36 @@ public class AnnotationProcessor { headerFile.append( "#ifndef " + getHeaderGuardName(HEADER_FILE) + "\n" + "#define " + getHeaderGuardName(HEADER_FILE) + "\n" + "\n" + "#include \"mozilla/jni/Refs.h\"\n" + "\n" + "namespace mozilla {\n" + - "namespace widget {\n" + + "namespace java {\n" + "\n"); implementationFile.append( "#include \"GeneratedJNIWrappers.h\"\n" + "#include \"mozilla/jni/Accessors.h\"\n" + "\n" + "namespace mozilla {\n" + - "namespace widget {\n" + + "namespace java {\n" + "\n"); nativesFile.append( "#ifndef " + getHeaderGuardName(NATIVES_FILE) + "\n" + "#define " + getHeaderGuardName(NATIVES_FILE) + "\n" + "\n" + "#include \"GeneratedJNIWrappers.h\"\n" + "#include \"mozilla/jni/Natives.h\"\n" + "\n" + "namespace mozilla {\n" + - "namespace widget {\n" + + "namespace java {\n" + "\n"); while (jarClassIterator.hasNext()) { generateClass(jarClassIterator.next()); } implementationFile.append( "} /* widget */\n" +
--- a/build/annotationProcessors/SDKProcessor.java +++ b/build/annotationProcessors/SDKProcessor.java @@ -72,27 +72,27 @@ public class SDKProcessor { StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT); headerFile.append( "#ifndef " + generatedFilePrefix + "_h__\n" + "#define " + generatedFilePrefix + "_h__\n" + "\n" + "#include \"mozilla/jni/Refs.h\"\n" + "\n" + "namespace mozilla {\n" + - "namespace widget {\n" + + "namespace java {\n" + "namespace sdk {\n" + "\n"); StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT); implementationFile.append( "#include \"" + generatedFilePrefix + ".h\"\n" + "#include \"mozilla/jni/Accessors.h\"\n" + "\n" + "namespace mozilla {\n" + - "namespace widget {\n" + + "namespace java {\n" + "namespace sdk {\n" + "\n"); // Used to track the calls to the various class-specific initialisation functions. ClassLoader loader = null; try { loader = URLClassLoader.newInstance(new URL[] { new URL("file://" + sdkJar) }, SDKProcessor.class.getClassLoader());
--- a/build/annotationProcessors/utils/Utils.java +++ b/build/annotationProcessors/utils/Utils.java @@ -7,16 +7,17 @@ package org.mozilla.gecko.annotationProc import org.mozilla.gecko.annotationProcessors.AnnotationInfo; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.nio.ByteBuffer; import java.util.HashMap; /** * A collection of utility methods used by CodeGenerator. Largely used for translating types. */ public class Utils { // A collection of lookup tables to simplify the functions to follow... @@ -91,16 +92,20 @@ public class Utils { // to C, generating the corresponding code using this Java program. Really?! return "mozilla::jni::Class::Param"; } if (type.equals(Throwable.class)) { return "mozilla::jni::Throwable::Param"; } + if (type.equals(ByteBuffer.class)) { + return "mozilla::jni::ByteBuffer::Param"; + } + return "mozilla::jni::Object::Param"; } public static String getNativeReturnType(Class<?> type, AnnotationInfo info) { final String name = type.getName().replace('.', '/'); if (NATIVE_TYPES.containsKey(name)) { return NATIVE_TYPES.get(name); @@ -123,16 +128,20 @@ public class Utils { // to C, generating the corresponding code using this Java program. Really?! return "mozilla::jni::Class::LocalRef"; } if (type.equals(Throwable.class)) { return "mozilla::jni::Throwable::LocalRef"; } + if (type.equals(ByteBuffer.class)) { + return "mozilla::jni::ByteBuffer::LocalRef"; + } + return "mozilla::jni::Object::LocalRef"; } /** * Get the JNI class descriptor corresponding to the provided type parameter. * * @param type Class to determine the corresponding JNI descriptor for. * @return Class descripor as a String
--- a/devtools/client/animationinspector/test/browser.ini +++ b/devtools/client/animationinspector/test/browser.ini @@ -11,16 +11,17 @@ support-files = doc_pseudo_elements.html doc_script_animation.html doc_simple_animation.html doc_multiple_animation_types.html head.js !/devtools/client/commandline/test/helpers.js !/devtools/client/framework/test/shared-head.js !/devtools/client/inspector/test/head.js + !/devtools/client/inspector/test/shared-head.js !/devtools/client/shared/test/test-actor-registry.js !/devtools/client/shared/test/test-actor.js [browser_animation_animated_properties_displayed.js] [browser_animation_click_selects_animation.js] [browser_animation_controller_exposes_document_currentTime.js] skip-if = os == "linux" && !debug # Bug 1234567 [browser_animation_empty_on_invalid_nodes.js]
--- a/devtools/client/canvasdebugger/canvasdebugger.js +++ b/devtools/client/canvasdebugger/canvasdebugger.js @@ -123,16 +123,22 @@ function shutdownCanvasDebugger() { /** * Functions handling target-related lifetime events. */ var EventsHandler = { /** * Listen for events emitted by the current tab target. */ initialize: function () { + // Make sure the backend is prepared to handle <canvas> contexts. + // Since actors are created lazily on the first request to them, we need to send an + // early request to ensure the CallWatcherActor is running and watching for new window + // globals. + gFront.setup({ reload: false }); + this._onTabNavigated = this._onTabNavigated.bind(this); gTarget.on("will-navigate", this._onTabNavigated); gTarget.on("navigate", this._onTabNavigated); }, /** * Remove events emitted by the current tab target. */ @@ -143,18 +149,16 @@ var EventsHandler = { /** * Called for each location change in the debugged tab. */ _onTabNavigated: function (event) { if (event != "will-navigate") { return; } - // Make sure the backend is prepared to handle <canvas> contexts. - gFront.setup({ reload: false }); // Reset UI. SnapshotsListView.empty(); CallsListView.empty(); $("#record-snapshot").removeAttribute("checked"); $("#record-snapshot").removeAttribute("disabled"); $("#record-snapshot").hidden = false;
--- a/devtools/client/canvasdebugger/test/head.js +++ b/devtools/client/canvasdebugger/test/head.js @@ -153,16 +153,17 @@ function once(aTarget, aEventName, aUseC for (let [add, remove] of [ ["on", "off"], // Use event emitter before DOM events for consistency ["addEventListener", "removeEventListener"], ["addListener", "removeListener"] ]) { if ((add in aTarget) && (remove in aTarget)) { aTarget[add](aEventName, function onEvent(...aArgs) { + info("Got event: '" + aEventName + "' on " + aTarget + "."); aTarget[remove](aEventName, onEvent, aUseCapture); deferred.resolve(...aArgs); }, aUseCapture); break; } } return deferred.promise;
--- a/devtools/client/commandline/test/browser.ini +++ b/devtools/client/commandline/test/browser.ini @@ -53,16 +53,17 @@ support-files = browser_cmd_csscoverage_sheetA.css browser_cmd_csscoverage_sheetB.css browser_cmd_csscoverage_sheetC.css browser_cmd_csscoverage_sheetD.css [browser_cmd_folder.js] skip-if = (e10s && debug) # Bug 1034511 (docShell leaks on debug) [browser_cmd_highlight_01.js] [browser_cmd_highlight_02.js] +skip-if = false [browser_cmd_highlight_03.js] [browser_cmd_inject.js] support-files = browser_cmd_inject.html [browser_cmd_csscoverage_util.js] skip-if = (e10s && debug) # Bug 1034511 (docShell leaks on debug) [browser_cmd_jsb.js] support-files =
--- a/devtools/client/commandline/test/browser_cmd_appcache_valid.js +++ b/devtools/client/commandline/test/browser_cmd_appcache_valid.js @@ -7,23 +7,23 @@ const TEST_URI = "http://sub1.test2.exam "commandline/test/browser_cmd_appcache_valid_index.html"; function test() { return Task.spawn(spawnTest).then(finish, helpers.handleError); } function* spawnTest() { let options = yield helpers.openTab(TEST_URI); - yield helpers.openToolbar(options); info("adding cache listener."); - // Wait for site to be cached. yield helpers.listenOnce(gBrowser.contentWindow.applicationCache, "cached"); + yield helpers.openToolbar(options); + // Pages containing an appcache the notification bar gives options to allow // or deny permission for the app to save data offline. Let's click Allow. let notificationID = "offline-app-requested-sub1.test2.example.com"; let notification = PopupNotifications.getNotification(notificationID, gBrowser.selectedBrowser); if (notification) { info("Authorizing offline storage."); notification.mainAction.callback();
--- a/devtools/client/commandline/test/browser_cmd_highlight_02.js +++ b/devtools/client/commandline/test/browser_cmd_highlight_02.js @@ -9,36 +9,37 @@ const TEST_PAGE = "data:text/html;charse function test() { return Task.spawn(function* () { let options = yield helpers.openTab(TEST_PAGE); yield helpers.openToolbar(options); info("highlighting the body node"); yield runCommand("highlight body", options); - is(getHighlighterNumber(), 1, "The highlighter element exists for body"); + is(yield getHighlighterNumber(), 1, "The highlighter element exists for body"); info("highlighting the div node"); yield runCommand("highlight div", options); - is(getHighlighterNumber(), 1, "The highlighter element exists for div"); + is(yield getHighlighterNumber(), 1, "The highlighter element exists for div"); info("highlighting the body node again, asking to keep the div"); yield runCommand("highlight body --keep", options); - is(getHighlighterNumber(), 2, "2 highlighter elements have been created"); + is(yield getHighlighterNumber(), 2, "2 highlighter elements have been created"); info("unhighlighting all nodes"); yield runCommand("unhighlight", options); - is(getHighlighterNumber(), 0, "All highlighters have been removed"); + is(yield getHighlighterNumber(), 0, "All highlighters have been removed"); yield helpers.closeToolbar(options); yield helpers.closeTab(options); }).then(finish, helpers.handleError); } function getHighlighterNumber() { - // Note that this only works as long as gcli tests aren't run with e10s on. - // To make this e10s ready, execute this in a content frame script instead. - return require("devtools/shared/gcli/commands/highlight").highlighters.length; + return ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () { + const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); + return require("devtools/shared/gcli/commands/highlight").highlighters.length; + }); } function* runCommand(cmd, options) { yield helpers.audit(options, [{ setup: cmd, exec: {} }]); }
--- a/devtools/client/debugger/test/mochitest/browser.ini +++ b/devtools/client/debugger/test/mochitest/browser.ini @@ -289,17 +289,17 @@ skip-if = (os == 'mac' && e10s && debug) [browser_dbg_interrupts.js] skip-if = e10s && debug [browser_dbg_listaddons.js] skip-if = e10s && debug tags = addons [browser_dbg_listtabs-01.js] skip-if = e10s # TODO [browser_dbg_listtabs-02.js] -skip-if = e10s # TODO +skip-if = true # Never worked for remote frames, needs a mock DebuggerServerConnection [browser_dbg_listtabs-03.js] skip-if = e10s && debug [browser_dbg_listworkers.js] [browser_dbg_location-changes-01-simple.js] skip-if = e10s && debug [browser_dbg_location-changes-02-blank.js] skip-if = e10s && debug [browser_dbg_location-changes-03-new.js]
--- a/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js +++ b/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js @@ -34,29 +34,27 @@ function test() { gClient.request({ to: globalActor, type: "ping" }, aResponse => { is(aResponse.pong, "pong", "Actor should respond to requests."); // Send another ping to see if the same actor is used. gClient.request({ to: globalActor, type: "ping" }, aResponse => { is(aResponse.pong, "pong", "Actor should respond to requests."); // Make sure that lazily-created actors are created only once. - let conn = transport._serverConnection; - - // First we look for the pool of global actors. - let extraPools = conn._extraPools; - let globalPool; + let count = 0; + for (let connID of Object.getOwnPropertyNames(DebuggerServer._connections)) { + let conn = DebuggerServer._connections[connID]; + let actorPrefix = conn._prefix + "test_one"; + for (let pool of conn._extraPools) { + count += Object.keys(pool._actors).filter(e => { + return e.startsWith(actorPrefix); + }).length; + } + } - let actorPrefix = conn._prefix + "test_one"; - let count = 0; - for (let pool of extraPools) { - count += Object.keys(pool._actors).filter(e => { - return e.startsWith(actorPrefix); - }).length; - } is(count, 2, "Only two actor exists in all pools. One tab actor and one global."); gClient.close(finish); }); }); }); });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js +++ b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js @@ -75,17 +75,18 @@ function testAttachRemovedTab() { let deferred = promise.defer(); gClient.addListener("paused", (aEvent, aPacket) => { ok(false, "Attaching to an exited tab actor shouldn't generate a pause."); deferred.reject(); }); gClient.request({ to: gTab2Actor, type: "attach" }, aResponse => { - is(aResponse.type, "exited", "Tab should consider itself exited."); + is(aResponse.error, "connectionClosed", + "Connection is gone since the tab was removed."); deferred.resolve(); }); return deferred.promise; }); } function closeConnection() {
--- a/devtools/client/framework/test/shared-head.js +++ b/devtools/client/framework/test/shared-head.js @@ -484,8 +484,13 @@ function pushPref(preferenceName, value) * @param {String} path * Dotted path to use to expand the object. * @return {?} anything that is found at the provided path in the object. */ function lookupPath(obj, path) { let segments = path.split("."); return segments.reduce((prev, current) => prev[current], obj); } + +var closeToolbox = Task.async(function* () { + let target = TargetFactory.forTab(gBrowser.selectedTab); + yield gDevTools.closeToolbox(target); +});
--- a/devtools/client/inspector/computed/test/browser.ini +++ b/devtools/client/inspector/computed/test/browser.ini @@ -6,18 +6,19 @@ support-files = doc_media_queries.html doc_pseudoelement.html doc_sourcemaps.css doc_sourcemaps.css.map doc_sourcemaps.html doc_sourcemaps.scss head.js !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js !/devtools/client/inspector/test/head.js - !/devtools/client/framework/test/shared-head.js + !/devtools/client/inspector/test/shared-head.js !/devtools/client/shared/test/test-actor.js !/devtools/client/shared/test/test-actor-registry.js [browser_computed_browser-styles.js] [browser_computed_cycle_color.js] [browser_computed_getNodeInfo.js] [browser_computed_keybindings_01.js] [browser_computed_keybindings_02.js]
--- a/devtools/client/inspector/fonts/test/browser.ini +++ b/devtools/client/inspector/fonts/test/browser.ini @@ -3,17 +3,18 @@ tags = devtools subsuite = devtools support-files = browser_fontinspector.html test_iframe.html ostrich-black.ttf ostrich-regular.ttf head.js !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js !/devtools/client/inspector/test/head.js - !/devtools/client/framework/test/shared-head.js + !/devtools/client/inspector/test/shared-head.js !/devtools/client/shared/test/test-actor.js !/devtools/client/shared/test/test-actor-registry.js [browser_fontinspector.js] [browser_fontinspector_edit-previews.js] [browser_fontinspector_edit-previews-show-all.js] [browser_fontinspector_theme-change.js]
--- a/devtools/client/inspector/layout/test/browser.ini +++ b/devtools/client/inspector/layout/test/browser.ini @@ -1,18 +1,19 @@ [DEFAULT] tags = devtools subsuite = devtools support-files = doc_layout_iframe1.html doc_layout_iframe2.html head.js !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js !/devtools/client/inspector/test/head.js - !/devtools/client/framework/test/shared-head.js + !/devtools/client/inspector/test/shared-head.js !/devtools/client/shared/test/test-actor.js !/devtools/client/shared/test/test-actor-registry.js [browser_layout.js] [browser_layout_editablemodel.js] # [browser_layout_editablemodel_allproperties.js] # Disabled for too many intermittent failures (bug 1009322) [browser_layout_editablemodel_bluronclick.js]
--- a/devtools/client/inspector/markup/test/browser.ini +++ b/devtools/client/inspector/markup/test/browser.ini @@ -42,18 +42,19 @@ support-files = lib_jquery_1.2_min.js lib_jquery_1.3_min.js lib_jquery_1.4_min.js lib_jquery_1.6_min.js lib_jquery_1.7_min.js lib_jquery_1.11.1_min.js lib_jquery_2.1.1_min.js !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js !/devtools/client/inspector/test/head.js - !/devtools/client/framework/test/shared-head.js + !/devtools/client/inspector/test/shared-head.js !/devtools/client/shared/test/test-actor.js !/devtools/client/shared/test/test-actor-registry.js [browser_markup_accessibility_focus_blur.js] skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences [browser_markup_accessibility_navigation.js] skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences [browser_markup_accessibility_navigation_after_edit.js]
--- a/devtools/client/inspector/rules/test/browser.ini +++ b/devtools/client/inspector/rules/test/browser.ini @@ -29,18 +29,19 @@ support-files = doc_sourcemaps.html doc_sourcemaps.scss doc_style_editor_link.css doc_test_image.png doc_urls_clickable.css doc_urls_clickable.html head.js !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js !/devtools/client/inspector/test/head.js - !/devtools/client/framework/test/shared-head.js + !/devtools/client/inspector/test/shared-head.js !/devtools/client/shared/test/test-actor.js !/devtools/client/shared/test/test-actor-registry.js [browser_rules_add-property-and-reselect.js] [browser_rules_add-property-cancel_01.js] [browser_rules_add-property-cancel_02.js] [browser_rules_add-property-cancel_03.js] [browser_rules_add-property-commented.js]
--- a/devtools/client/inspector/shared/test/browser.ini +++ b/devtools/client/inspector/shared/test/browser.ini @@ -8,18 +8,19 @@ support-files = doc_content_stylesheet_imported.css doc_content_stylesheet_imported2.css doc_content_stylesheet_linked.css doc_content_stylesheet_script.css doc_content_stylesheet_xul.css doc_frame_script.js head.js !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js !/devtools/client/inspector/test/head.js - !/devtools/client/framework/test/shared-head.js + !/devtools/client/inspector/test/shared-head.js !/devtools/client/shared/test/test-actor.js !/devtools/client/shared/test/test-actor-registry.js [browser_styleinspector_context-menu-copy-color_01.js] [browser_styleinspector_context-menu-copy-color_02.js] subsuite = clipboard [browser_styleinspector_context-menu-copy-urls.js] subsuite = clipboard
--- a/devtools/client/inspector/test/browser.ini +++ b/devtools/client/inspector/test/browser.ini @@ -26,18 +26,18 @@ support-files = doc_inspector_remove-iframe-during-load.html doc_inspector_search.html doc_inspector_search-reserved.html doc_inspector_search-suggestions.html doc_inspector_search-svg.html doc_inspector_select-last-selected-01.html doc_inspector_select-last-selected-02.html head.js + shared-head.js !/devtools/client/commandline/test/helpers.js - !/devtools/client/inspector/test/head.js !/devtools/client/framework/test/shared-head.js !/devtools/client/shared/test/test-actor.js !/devtools/client/shared/test/test-actor-registry.js [browser_inspector_addNode_01.js] [browser_inspector_addNode_02.js] [browser_inspector_addNode_03.js] [browser_inspector_breadcrumbs.js]
--- a/devtools/client/inspector/test/head.js +++ b/devtools/client/inspector/test/head.js @@ -1,17 +1,17 @@ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* eslint no-unused-vars: [2, {"vars": "local"}] */ /* import-globals-from ../../framework/test/shared-head.js */ /* import-globals-from ../../commandline/test/helpers.js */ /* import-globals-from ../../shared/test/test-actor-registry.js */ -/* globals registerTestActor, getTestActor */ +/* import-globals-from ../../inspector/test/shared-head.js */ "use strict"; // Load the shared-head file first. Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this); // Services.prefs.setBoolPref("devtools.debugger.log", true); @@ -24,16 +24,21 @@ Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/commandline/test/helpers.js", this); // Import helpers registering the test-actor in remote targets Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/shared/test/test-actor-registry.js", this); +// Import helpers for the inspector that are also shared with others +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this); + DevToolsUtils.testing = true; registerCleanupFunction(() => { DevToolsUtils.testing = false; }); registerCleanupFunction(() => { Services.prefs.clearUserPref("devtools.inspector.activeSidebar"); }); @@ -117,34 +122,16 @@ var startPicker = Task.async(function* ( * node */ function selectAndHighlightNode(selector, inspector) { info("Highlighting and selecting the node " + selector); return selectNode(selector, inspector, "test-highlight"); } /** - * Set the inspector's current selection to the first match of the given css - * selector - * @param {String|NodeFront} selector - * @param {InspectorPanel} inspector The instance of InspectorPanel currently - * loaded in the toolbox - * @param {String} reason Defaults to "test" which instructs the inspector not - * to highlight the node upon selection - * @return {Promise} Resolves when the inspector is updated with the new node - */ -var selectNode = Task.async(function* (selector, inspector, reason = "test") { - info("Selecting the node for '" + selector + "'"); - let nodeFront = yield getNodeFront(selector, inspector); - let updated = inspector.once("inspector-updated"); - inspector.selection.setNodeFront(nodeFront, reason); - yield updated; -}); - -/** * Select node for a given selector, make it focusable and set focus in its * container element. * @param {String|NodeFront} selector * @param {InspectorPanel} inspector The current inspector-panel instance. * @return {MarkupContainer} */ function* focusNode(selector, inspector) { getContainerForNodeFront(inspector.walker.rootNode, inspector).elt.focus(); @@ -177,42 +164,16 @@ function clearCurrentNodeSelection(inspe * with an object: { tab, toolbox, inspector }. */ var openInspectorForURL = Task.async(function* (url, hostType) { let tab = yield addTab(url); let { inspector, toolbox, testActor } = yield openInspector(hostType); return { tab, inspector, toolbox, testActor }; }); -/** - * Open the toolbox, with the inspector tool visible. - * @param {String} hostType Optional hostType, as defined in Toolbox.HostType - * @return a promise that resolves when the inspector is ready - */ -var openInspector = Task.async(function* (hostType) { - info("Opening the inspector"); - - let toolbox = yield openToolboxForTab(gBrowser.selectedTab, "inspector", - hostType); - let inspector = toolbox.getPanel("inspector"); - - if (inspector._updateProgress) { - info("Need to wait for the inspector to update"); - yield inspector.once("inspector-updated"); - } - - info("Waiting for actor features to be detected"); - yield inspector._detectingActorFeatures; - - yield registerTestActor(toolbox.target.client); - let testActor = yield getTestActor(toolbox); - - return {toolbox, inspector, testActor}; -}); - function getActiveInspector() { let target = TargetFactory.forTab(gBrowser.selectedTab); return gDevTools.getToolbox(target).getPanel("inspector"); } /** * Right click on a node in the test page and click on the inspect menu item. * @param {TestActor} @@ -240,113 +201,16 @@ var clickOnInspectMenuItem = Task.async( let contextClosed = once(contentAreaContextMenu, "popuphidden"); contentAreaContextMenu.hidePopup(); yield contextClosed; return getActiveInspector(); }); /** - * Open the toolbox, with the inspector tool visible, and the one of the sidebar - * tabs selected. - * - * @param {String} id - * The ID of the sidebar tab to be opened - * @return a promise that resolves when the inspector is ready and the tab is - * visible and ready - */ -var openInspectorSidebarTab = Task.async(function* (id) { - let {toolbox, inspector, testActor} = yield openInspector(); - - info("Selecting the " + id + " sidebar"); - inspector.sidebar.select(id); - - return { - toolbox, - inspector, - testActor - }; -}); - -/** - * Open the toolbox, with the inspector tool visible, and the rule-view - * sidebar tab selected. - * - * @return a promise that resolves when the inspector is ready and the rule view - * is visible and ready - */ -function openRuleView() { - return openInspectorSidebarTab("ruleview").then(data => { - return { - toolbox: data.toolbox, - inspector: data.inspector, - testActor: data.testActor, - view: data.inspector.ruleview.view - }; - }); -} - -/** - * Open the toolbox, with the inspector tool visible, and the computed-view - * sidebar tab selected. - * - * @return a promise that resolves when the inspector is ready and the computed - * view is visible and ready - */ -function openComputedView() { - return openInspectorSidebarTab("computedview").then(data => { - return { - toolbox: data.toolbox, - inspector: data.inspector, - testActor: data.testActor, - view: data.inspector.computedview.computedView - }; - }); -} - -/** - * Select the rule view sidebar tab on an already opened inspector panel. - * - * @param {InspectorPanel} inspector - * The opened inspector panel - * @return {CssRuleView} the rule view - */ -function selectRuleView(inspector) { - inspector.sidebar.select("ruleview"); - return inspector.ruleview.view; -} - -/** - * Select the computed view sidebar tab on an already opened inspector panel. - * - * @param {InspectorPanel} inspector - * The opened inspector panel - * @return {CssComputedView} the computed view - */ -function selectComputedView(inspector) { - inspector.sidebar.select("computedview"); - return inspector.computedview.computedView; -} - -/** - * Get the NodeFront for a node that matches a given css selector, via the - * protocol. - * @param {String|NodeFront} selector - * @param {InspectorPanel} inspector The instance of InspectorPanel currently - * loaded in the toolbox - * @return {Promise} Resolves to the NodeFront instance - */ -function getNodeFront(selector, {walker}) { - if (selector._form) { - return selector; - } - return walker.querySelector(walker.rootNode, selector); -} - -/** * Get the NodeFront for a node that matches a given css selector inside a * given iframe. * @param {String|NodeFront} selector * @param {String|NodeFront} frameSelector A selector that matches the iframe * the node is in * @param {InspectorPanel} inspector The instance of InspectorPanel currently * loaded in the toolbox * @return {Promise} Resolves when the inspector is updated with the new node
new file mode 100644 --- /dev/null +++ b/devtools/client/inspector/test/shared-head.js @@ -0,0 +1,152 @@ +/* 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/. */ + +"use strict"; + +/* eslint no-unused-vars: [2, {"vars": "local"}] */ +/* globals registerTestActor, getTestActor, Task, openToolboxForTab, gBrowser */ + +// This file contains functions related to the inspector that are also of interest to +// other test directores as well. + +/** + * Open the toolbox, with the inspector tool visible. + * @param {String} hostType Optional hostType, as defined in Toolbox.HostType + * @return a promise that resolves when the inspector is ready + */ +var openInspector = Task.async(function* (hostType) { + info("Opening the inspector"); + + let toolbox = yield openToolboxForTab(gBrowser.selectedTab, "inspector", + hostType); + let inspector = toolbox.getPanel("inspector"); + + if (inspector._updateProgress) { + info("Need to wait for the inspector to update"); + yield inspector.once("inspector-updated"); + } + + info("Waiting for actor features to be detected"); + yield inspector._detectingActorFeatures; + + yield registerTestActor(toolbox.target.client); + let testActor = yield getTestActor(toolbox); + + return {toolbox, inspector, testActor}; +}); + +/** + * Open the toolbox, with the inspector tool visible, and the one of the sidebar + * tabs selected. + * + * @param {String} id + * The ID of the sidebar tab to be opened + * @return a promise that resolves when the inspector is ready and the tab is + * visible and ready + */ +var openInspectorSidebarTab = Task.async(function* (id) { + let {toolbox, inspector, testActor} = yield openInspector(); + + info("Selecting the " + id + " sidebar"); + inspector.sidebar.select(id); + + return { + toolbox, + inspector, + testActor + }; +}); + +/** + * Open the toolbox, with the inspector tool visible, and the rule-view + * sidebar tab selected. + * + * @return a promise that resolves when the inspector is ready and the rule view + * is visible and ready + */ +function openRuleView() { + return openInspectorSidebarTab("ruleview").then(data => { + return { + toolbox: data.toolbox, + inspector: data.inspector, + testActor: data.testActor, + view: data.inspector.ruleview.view + }; + }); +} + +/** + * Open the toolbox, with the inspector tool visible, and the computed-view + * sidebar tab selected. + * + * @return a promise that resolves when the inspector is ready and the computed + * view is visible and ready + */ +function openComputedView() { + return openInspectorSidebarTab("computedview").then(data => { + return { + toolbox: data.toolbox, + inspector: data.inspector, + testActor: data.testActor, + view: data.inspector.computedview.computedView + }; + }); +} + +/** + * Select the rule view sidebar tab on an already opened inspector panel. + * + * @param {InspectorPanel} inspector + * The opened inspector panel + * @return {CssRuleView} the rule view + */ +function selectRuleView(inspector) { + inspector.sidebar.select("ruleview"); + return inspector.ruleview.view; +} + +/** + * Select the computed view sidebar tab on an already opened inspector panel. + * + * @param {InspectorPanel} inspector + * The opened inspector panel + * @return {CssComputedView} the computed view + */ +function selectComputedView(inspector) { + inspector.sidebar.select("computedview"); + return inspector.computedview.computedView; +} + +/** + * Get the NodeFront for a node that matches a given css selector, via the + * protocol. + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @return {Promise} Resolves to the NodeFront instance + */ +function getNodeFront(selector, {walker}) { + if (selector._form) { + return selector; + } + return walker.querySelector(walker.rootNode, selector); +} + +/** + * Set the inspector's current selection to the first match of the given css + * selector + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @param {String} reason Defaults to "test" which instructs the inspector not + * to highlight the node upon selection + * @return {Promise} Resolves when the inspector is updated with the new node + */ +var selectNode = Task.async(function* (selector, inspector, reason = "test") { + info("Selecting the node for '" + selector + "'"); + let nodeFront = yield getNodeFront(selector, inspector); + let updated = inspector.once("inspector-updated"); + inspector.selection.setNodeFront(nodeFront, reason); + yield updated; +});
--- a/devtools/client/responsive.html/browser/tunnel.js +++ b/devtools/client/responsive.html/browser/tunnel.js @@ -88,22 +88,16 @@ function tunnelToInnerBrowser(outer, inn // Replace the outer browser's native messageManager with a message manager tunnel // which we can use to route messages of interest to the inner browser instead. // Note: The _actual_ messageManager accessible from // `browser.frameLoader.messageManager` is not overridable and is left unchanged. // Only the XBL getter `browser.messageManager` is overridden. Browser UI code // always uses this getter instead of `browser.frameLoader.messageManager` directly, // so this has the effect of overriding the message manager for browser UI code. mmTunnel = new MessageManagerTunnel(outer, inner); - Object.defineProperty(outer, "messageManager", { - value: mmTunnel, - writable: false, - configurable: true, - enumerable: true, - }); // We are tunneling to an inner browser with a specific remoteness, so it is simpler // for the logic of the browser UI to assume this tab has taken on that remoteness, // even though it's not true. Since the actions the browser UI performs are sent // down to the inner browser by this tunnel, the tab's remoteness effectively is the // remoteness of the inner browser. Object.defineProperty(outer, "isRemoteBrowser", { get() { @@ -206,17 +200,16 @@ function tunnelToInnerBrowser(outer, inn // Reset the XBL binding back to the default. outer.destroy(); outer.style.MozBinding = ""; // Reset overridden XBL properties and methods. Deleting the override // means it will fallback to the original XBL binding definitions which // are on the prototype. - delete outer.messageManager; delete outer.isRemoteBrowser; delete outer.hasContentOpener; delete outer.docShellIsActive; delete outer.setDocShellIsActiveAndForeground; mmTunnel.destroy(); mmTunnel = null; @@ -263,44 +256,51 @@ function copyPermanentKey(outer, inner) * without having to touch the original code paths that use them. */ function MessageManagerTunnel(outer, inner) { if (outer.isRemoteBrowser) { throw new Error("The outer browser must be non-remote."); } this.outer = outer; this.inner = inner; + this.tunneledMessageNames = new Set(); this.init(); } MessageManagerTunnel.prototype = { /** * Most message manager methods are left alone and are just passed along to - * the outer browser's real message manager. `sendAsyncMessage` is only one - * with special behavior. + * the outer browser's real message manager. */ PASS_THROUGH_METHODS: [ - "addMessageListener", - "loadFrameScript", "killChild", "assertPermission", "assertContainApp", "assertAppHasPermission", "assertAppHasStatus", "removeDelayedFrameScript", "getDelayedFrameScripts", "loadProcessScript", "removeDelayedProcessScript", "getDelayedProcessScripts", - "removeMessageListener", "addWeakMessageListener", "removeWeakMessageListener", ], + /** + * The following methods are overridden with special behavior while tunneling. + */ + OVERRIDDEN_METHODS: [ + "loadFrameScript", + "addMessageListener", + "removeMessageListener", + "sendAsyncMessage", + ], + OUTER_TO_INNER_MESSAGES: [ // Messages sent from remote-browser.xml "Browser:PurgeSessionHistory", "InPermitUnload", "PermitUnload", // Messages sent from browser.js "Browser:Reload", // Messages sent from SelectParentHelper.jsm @@ -333,69 +333,197 @@ MessageManagerTunnel.prototype = { "PageVisibility:Hide", "PageVisibility:Show", // Messages sent to SessionStore.jsm "SessionStore:update", // Messages sent to BrowserTestUtils.jsm "browser-test-utils:loadEvent", ], + OUTER_TO_INNER_MESSAGE_PREFIXES: [ + // Messages sent from DevTools + "debug:", + ], + + INNER_TO_OUTER_MESSAGE_PREFIXES: [ + // Messages sent to DevTools + "debug:", + ], + + OUTER_TO_INNER_FRAME_SCRIPTS: [ + // DevTools server for OOP frames + "resource://devtools/server/child.js" + ], + get outerParentMM() { + if (!this.outer.frameLoader) { + return null; + } return this.outer.frameLoader.messageManager; }, get outerChildMM() { // This is only possible because we require the outer browser to be // non-remote, so we're able to reach into its window and use the child // side message manager there. let docShell = this.outer.frameLoader.docShell; return docShell.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIContentFrameMessageManager); }, get innerParentMM() { + if (!this.inner.frameLoader) { + return null; + } return this.inner.frameLoader.messageManager; }, + init() { + for (let method of this.PASS_THROUGH_METHODS) { + // Workaround bug 449811 to ensure a fresh binding each time through the loop + let _method = method; + this[_method] = (...args) => { + if (!this.outerParentMM) { + return null; + } + return this.outerParentMM[_method](...args); + }; + } + + for (let name of this.INNER_TO_OUTER_MESSAGES) { + this.innerParentMM.addMessageListener(name, this); + this.tunneledMessageNames.add(name); + } + + Services.obs.addObserver(this, "message-manager-close", false); + + // Replace the outer browser's messageManager with this tunnel + Object.defineProperty(this.outer, "messageManager", { + value: this, + writable: false, + configurable: true, + enumerable: true, + }); + }, + + destroy() { + if (this.destroyed) { + return; + } + this.destroyed = true; + debug("Destroy tunnel"); + + // Watch for the messageManager to close. In most cases, the caller will stop the + // tunnel gracefully before this, but when the browser window closes or application + // exits, we may not see the high-level close events. + Services.obs.removeObserver(this, "message-manager-close"); + + // Reset the messageManager. Deleting the override means it will fallback to the + // original XBL binding definitions which are on the prototype. + delete this.outer.messageManager; + + for (let name of this.tunneledMessageNames) { + this.innerParentMM.removeMessageListener(name, this); + } + + // Some objects may have cached this tunnel as the messageManager for a frame. To + // ensure it keeps working after tunnel close, rewrite the overidden methods as pass + // through methods. + for (let method of this.OVERRIDDEN_METHODS) { + // Workaround bug 449811 to ensure a fresh binding each time through the loop + let _method = method; + this[_method] = (...args) => { + if (!this.outerParentMM) { + return null; + } + return this.outerParentMM[_method](...args); + }; + } + }, + + observe(subject, topic, data) { + if (topic != "message-manager-close") { + return; + } + if (subject == this.innerParentMM) { + debug("Inner messageManager has closed"); + this.destroy(); + } + if (subject == this.outerParentMM) { + debug("Outer messageManager has closed"); + this.destroy(); + } + }, + + loadFrameScript(url, ...args) { + debug(`Calling loadFrameScript for ${url}`); + + if (!this.OUTER_TO_INNER_FRAME_SCRIPTS.includes(url)) { + debug(`Should load ${url} into inner?`); + this.outerParentMM.loadFrameScript(url, ...args); + return; + } + + debug(`Load ${url} into inner`); + this.innerParentMM.loadFrameScript(url, ...args); + }, + + addMessageListener(name, ...args) { + debug(`Calling addMessageListener for ${name}`); + + debug(`Add outer listener for ${name}`); + // Add an outer listener, just like a simple pass through + this.outerParentMM.addMessageListener(name, ...args); + + // If the message name is part of a prefix we're tunneling, we also need to add the + // tunnel as an inner listener. + if (this.INNER_TO_OUTER_MESSAGE_PREFIXES.some(prefix => name.startsWith(prefix))) { + debug(`Add inner listener for ${name}`); + this.innerParentMM.addMessageListener(name, this); + this.tunneledMessageNames.add(name); + } + }, + + removeMessageListener(name, ...args) { + debug(`Calling removeMessageListener for ${name}`); + + debug(`Remove outer listener for ${name}`); + // Remove an outer listener, just like a simple pass through + this.outerParentMM.removeMessageListener(name, ...args); + + // Leave the tunnel as an inner listener for the case of prefix messages to avoid + // tracking counts of add calls. The inner listener will get removed on destroy. + }, + sendAsyncMessage(name, ...args) { debug(`Calling sendAsyncMessage for ${name}`); - if (!this.OUTER_TO_INNER_MESSAGES.includes(name)) { + if (!this._shouldTunnelOuterToInner(name)) { debug(`Should ${name} go to inner?`); this.outerParentMM.sendAsyncMessage(name, ...args); return; } debug(`${name} outer -> inner`); this.innerParentMM.sendAsyncMessage(name, ...args); }, - init() { - for (let method of this.PASS_THROUGH_METHODS) { - // Workaround bug 449811 to ensure a fresh binding each time through the loop - let _method = method; - this[_method] = (...args) => { - return this.outerParentMM[_method](...args); - }; - } - - for (let message of this.INNER_TO_OUTER_MESSAGES) { - this.innerParentMM.addMessageListener(message, this); - } - }, - - destroy() { - for (let message of this.INNER_TO_OUTER_MESSAGES) { - this.innerParentMM.removeMessageListener(message, this); - } - }, - receiveMessage({ name, data, objects, principal }) { - if (!this.INNER_TO_OUTER_MESSAGES.includes(name)) { + if (!this._shouldTunnelInnerToOuter(name)) { debug(`Received unexpected message ${name}`); return; } debug(`${name} inner -> outer`); this.outerChildMM.sendAsyncMessage(name, data, objects, principal); }, + _shouldTunnelOuterToInner(name) { + return this.OUTER_TO_INNER_MESSAGES.includes(name) || + this.OUTER_TO_INNER_MESSAGE_PREFIXES.some(prefix => name.startsWith(prefix)); + }, + + _shouldTunnelInnerToOuter(name) { + return this.INNER_TO_OUTER_MESSAGES.includes(name) || + this.INNER_TO_OUTER_MESSAGE_PREFIXES.some(prefix => name.startsWith(prefix)); + }, + };
--- a/devtools/client/responsive.html/manager.js +++ b/devtools/client/responsive.html/manager.js @@ -257,42 +257,39 @@ ResponsiveUI.prototype = { * Open RDM while preserving the state of the page. We use `swapFrameLoaders` * to ensure all in-page state is preserved, just like when you move a tab to * a new window. * * For more details, see /devtools/docs/responsive-design-mode.md. */ init: Task.async(function* () { let ui = this; - let toolViewportContentBrowser; - // Watch for the tab's beforeunload to catch app shutdown early - this.tab.linkedBrowser.addEventListener("beforeunload", this, true); + // Watch for tab close so we can clean up RDM synchronously + this.tab.addEventListener("TabClose", this); // Swap page content from the current tab into a viewport within RDM this.swap = swapToInnerBrowser({ tab: this.tab, containerURL: TOOL_URL, getInnerBrowser: Task.async(function* (containerBrowser) { let toolWindow = ui.toolWindow = containerBrowser.contentWindow; toolWindow.addEventListener("message", ui); yield message.request(toolWindow, "init"); toolWindow.addInitialViewport("about:blank"); yield message.wait(toolWindow, "browser-mounted"); - toolViewportContentBrowser = ui.getViewportBrowser(); - return toolViewportContentBrowser; + return ui.getViewportBrowser(); }) }); yield this.swap.start(); // Notify the inner browser to start the frame script yield message.request(this.toolWindow, "start-frame-script"); - this.touchEventSimulator = - new TouchEventSimulator(toolViewportContentBrowser); + this.touchEventSimulator = new TouchEventSimulator(this.getViewportBrowser()); }), /** * Close RDM and restore page content back into a regular tab. * * @param object * Destroy options, which currently includes a `reason` string. * @return boolean @@ -300,36 +297,36 @@ ResponsiveUI.prototype = { * was already in progress. */ destroy: Task.async(function* (options) { if (this.destroying) { return false; } this.destroying = true; - // If our tab is about to be unloaded, there's not enough time to exit + // If our tab is about to be closed, there's not enough time to exit // gracefully, but that shouldn't be a problem since the tab will go away. - // So, skip any yielding when we're about to unload. - let isBeforeUnload = options && options.reason == "beforeunload"; + // So, skip any yielding when we're about to close the tab. + let isTabClosing = options && options.reason == "TabClose"; // Ensure init has finished before starting destroy - if (!isBeforeUnload) { + if (!isTabClosing) { yield this.inited; } - this.tab.linkedBrowser.removeEventListener("beforeunload", this, true); + this.tab.removeEventListener("TabClose", this); this.toolWindow.removeEventListener("message", this); // Stop the touch event simulator if it was running - if (!isBeforeUnload) { + if (!isTabClosing) { yield this.touchEventSimulator.stop(); } // Notify the inner browser to stop the frame script - if (!isBeforeUnload) { + if (!isTabClosing) { yield message.request(this.toolWindow, "stop-frame-script"); } // Destroy local state let swap = this.swap; this.browserWindow = null; this.tab = null; this.inited = null; @@ -347,29 +344,17 @@ ResponsiveUI.prototype = { handleEvent(event) { let { browserWindow, tab } = this; switch (event.type) { case "message": this.handleMessage(event); break; - case "beforeunload": - // Ignore the event for locations like about:blank - if (event.target.location != TOOL_URL) { - return; - } - // We want to ensure we close RDM before browser window close when - // quitting. Session restore starts its final session data flush when - // it gets the `quit-application-granted` notification. If we were to - // wait for browser windows to close before destroying (which will undo - // the content swap), session restore gets wedged in a confused state - // since we're moving browsers at the same time that it is blocking - // shutdown on messages from each browser. So instead, we destroy - // earlier based on the tab's `beforeunload` event. + case "TabClose": ResponsiveUIManager.closeIfNeeded(browserWindow, tab, { reason: event.type, }); break; } }, handleMessage(event) { @@ -425,11 +410,18 @@ ResponsiveUI.prototype = { /** * Helper for tests/reloading the viewport. Assumes a single viewport for now. */ getViewportBrowser() { return this.toolWindow.getViewportBrowser(); }, + /** + * Helper for contacting the viewport content. Assumes a single viewport for now. + */ + getViewportMessageManager() { + return this.getViewportBrowser().messageManager; + }, + }; EventEmitter.decorate(ResponsiveUI.prototype);
--- a/devtools/client/responsive.html/test/browser/browser.ini +++ b/devtools/client/responsive.html/test/browser/browser.ini @@ -5,26 +5,31 @@ subsuite = devtools skip-if = !e10s support-files = devices.json doc_page_state.html head.js !/devtools/client/commandline/test/helpers.js !/devtools/client/framework/test/shared-head.js !/devtools/client/framework/test/shared-redux-head.js + !/devtools/client/inspector/test/shared-head.js + !/devtools/client/shared/test/test-actor.js + !/devtools/client/shared/test/test-actor-registry.js [browser_device_modal_error.js] [browser_device_modal_exit.js] [browser_device_modal_submit.js] [browser_device_width.js] [browser_exit_button.js] [browser_frame_script_active.js] [browser_menu_item_01.js] [browser_menu_item_02.js] [browser_mouse_resize.js] [browser_navigation.js] [browser_page_state.js] [browser_resize_cmd.js] -skip-if = true # GCLI target confused after swap, will fix in bug 1240907 +skip-if = true # GCLI target confused after swap, will fix in bug 1240912 [browser_screenshot_button.js] [browser_shutdown_close_sync.js] +[browser_toolbox_computed_view.js] +[browser_toolbox_rule_view.js] [browser_touch_simulation.js] [browser_viewport_basics.js]
--- a/devtools/client/responsive.html/test/browser/browser_shutdown_close_sync.js +++ b/devtools/client/responsive.html/test/browser/browser_shutdown_close_sync.js @@ -1,24 +1,38 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; -// Verify RDM closes synchronously in the case of the "beforeunload" event. +// Verify RDM closes synchronously when tabs are closed. const TEST_URL = "http://example.com/"; + add_task(function* () { let tab = yield addTab(TEST_URL); let { ui } = yield openRDM(tab); closeRDM(tab, { - reason: "beforeunload", + reason: "TabClose", }); // This flag is set at the end of `ResponsiveUI.destroy`. If it is true // without yielding on `closeRDM` above, then we must have closed // synchronously. is(ui.destroyed, true, "RDM closed synchronously"); yield removeTab(tab); }); + +add_task(function* () { + let tab = yield addTab(TEST_URL); + + let { ui } = yield openRDM(tab); + + yield removeTab(tab); + + // This flag is set at the end of `ResponsiveUI.destroy`. If it is true without + // yielding on `closeRDM` itself and only removing the tab, then we must have closed + // synchronously in response to tab closing. + is(ui.destroyed, true, "RDM closed synchronously"); +});
new file mode 100644 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_toolbox_computed_view.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that when the viewport is resized, the computed-view refreshes. + +const TEST_URI = "data:text/html;charset=utf-8,<html><style>" + + "div {" + + " width: 500px;" + + " height: 10px;" + + " background: purple;" + + "} " + + "@media screen and (max-width: 200px) {" + + " div { " + + " width: 100px;" + + " }" + + "};" + + "</style><div></div></html>"; + +addRDMTask(TEST_URI, function* ({ ui, manager }) { + info("Open the responsive design mode and set its size to 500x500 to start"); + yield setViewportSize(ui, manager, 500, 500); + + info("Open the inspector, computed-view and select the test node"); + let { inspector, view } = yield openComputedView(); + yield selectNode("div", inspector); + + info("Try shrinking the viewport and checking the applied styles"); + yield testShrink(view, inspector, ui, manager); + + info("Try growing the viewport and checking the applied styles"); + yield testGrow(view, inspector, ui, manager); + + yield closeToolbox(); +}); + +function* testShrink(computedView, inspector, ui, manager) { + is(computedWidth(computedView), "500px", "Should show 500px initially."); + + let onRefresh = inspector.once("computed-view-refreshed"); + yield setViewportSize(ui, manager, 100, 100); + yield onRefresh; + + is(computedWidth(computedView), "100px", "Should be 100px after shrinking."); +} + +function* testGrow(computedView, inspector, ui, manager) { + let onRefresh = inspector.once("computed-view-refreshed"); + yield setViewportSize(ui, manager, 500, 500); + yield onRefresh; + + is(computedWidth(computedView), "500px", "Should be 500px after growing."); +} + +function computedWidth(computedView) { + for (let prop of computedView.propertyViews) { + if (prop.name === "width") { + return prop.valueNode.textContent; + } + } + return null; +}
new file mode 100644 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_toolbox_rule_view.js @@ -0,0 +1,78 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that when the viewport is resized, the rule-view refreshes. + +const TEST_URI = "data:text/html;charset=utf-8,<html><style>" + + "div {" + + " width: 500px;" + + " height: 10px;" + + " background: purple;" + + "} " + + "@media screen and (max-width: 200px) {" + + " div { " + + " width: 100px;" + + " }" + + "};" + + "</style><div></div></html>"; + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled"); +}); + +addRDMTask(TEST_URI, function* ({ ui, manager }) { + info("Open the responsive design mode and set its size to 500x500 to start"); + yield setViewportSize(ui, manager, 500, 500); + + info("Open the inspector, rule-view and select the test node"); + let { inspector, view } = yield openRuleView(); + yield selectNode("div", inspector); + + info("Try shrinking the viewport and checking the applied styles"); + yield testShrink(view, ui, manager); + + info("Try growing the viewport and checking the applied styles"); + yield testGrow(view, ui, manager); + + info("Check that ESC still opens the split console"); + yield testEscapeOpensSplitConsole(inspector); + + yield closeToolbox(); +}); + +function* testShrink(ruleView, ui, manager) { + is(numberOfRules(ruleView), 2, "Should have two rules initially."); + + info("Resize to 100x100 and wait for the rule-view to update"); + let onRefresh = ruleView.once("ruleview-refreshed"); + yield setViewportSize(ui, manager, 100, 100); + yield onRefresh; + + is(numberOfRules(ruleView), 3, "Should have three rules after shrinking."); +} + +function* testGrow(ruleView, ui, manager) { + info("Resize to 500x500 and wait for the rule-view to update"); + let onRefresh = ruleView.once("ruleview-refreshed"); + yield setViewportSize(ui, manager, 500, 500); + yield onRefresh; + + is(numberOfRules(ruleView), 2, "Should have two rules after growing."); +} + +function* testEscapeOpensSplitConsole(inspector) { + ok(!inspector._toolbox._splitConsole, "Console is not split."); + + info("Press escape"); + let onSplit = inspector._toolbox.once("split-console"); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield onSplit; + + ok(inspector._toolbox._splitConsole, "Console is split after pressing ESC."); +} + +function numberOfRules(ruleView) { + return ruleView.element.querySelectorAll(".ruleview-code").length; +}
--- a/devtools/client/responsive.html/test/browser/head.js +++ b/devtools/client/responsive.html/test/browser/head.js @@ -2,51 +2,66 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; /* eslint no-unused-vars: [2, {"vars": "local"}] */ /* import-globals-from ../../../framework/test/shared-head.js */ /* import-globals-from ../../../framework/test/shared-redux-head.js */ /* import-globals-from ../../../commandline/test/helpers.js */ +/* import-globals-from ../../../inspector/test/shared-head.js */ Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this); Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/framework/test/shared-redux-head.js", this); // Import the GCLI test helper Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/commandline/test/helpers.js", this); +// Import helpers registering the test-actor in remote targets +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/shared/test/test-actor-registry.js", + this); + +// Import helpers for the inspector that are also shared with others +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this); + const TEST_URI_ROOT = "http://example.com/browser/devtools/client/responsive.html/test/browser/"; +const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL"; + +const { _loadPreferredDevices } = require("devtools/client/responsive.html/actions/devices"); +const { getOwnerWindow } = require("sdk/tabs/utils"); +const asyncStorage = require("devtools/shared/async-storage"); SimpleTest.requestCompleteLog(); SimpleTest.waitForExplicitFinish(); DevToolsUtils.testing = true; Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList"); Services.prefs.setCharPref("devtools.devices.url", TEST_URI_ROOT + "devices.json"); Services.prefs.setBoolPref("devtools.responsive.html.enabled", true); registerCleanupFunction(() => { DevToolsUtils.testing = false; Services.prefs.clearUserPref("devtools.devices.url"); Services.prefs.clearUserPref("devtools.responsive.html.enabled"); Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList"); + asyncStorage.removeItem("devtools.devices.url_cache"); }); + +// This depends on the "devtools.responsive.html.enabled" pref const { ResponsiveUIManager } = require("resource://devtools/client/responsivedesign/responsivedesign.jsm"); -const { _loadPreferredDevices } = require("devtools/client/responsive.html/actions/devices"); -const { getOwnerWindow } = require("sdk/tabs/utils"); - -const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL"; /** * Open responsive design mode for the given tab. */ var openRDM = Task.async(function* (tab) { info("Opening responsive design mode"); let manager = ResponsiveUIManager; let ui = yield manager.openIfNeeded(getOwnerWindow(tab), tab);
--- a/devtools/client/shared/test/test-actor-registry.js +++ b/devtools/client/shared/test/test-actor-registry.js @@ -1,105 +1,97 @@ /* 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/. */ "use strict"; (function (exports) { - - var Cu = Components.utils; - var Ci = Components.interfaces; - var Cc = Components.classes; - var CC = Components.Constructor; + const Cu = Components.utils; + const CC = Components.Constructor; - var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); - var { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {}); - var { fetch } = require("devtools/shared/DevToolsUtils"); - var promise = require("promise"); - var defer = require("devtools/shared/defer"); + const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); + const { fetch } = require("devtools/shared/DevToolsUtils"); + const defer = require("devtools/shared/defer"); + const { Task } = require("devtools/shared/task"); - var TEST_URL_ROOT = "http://example.com/browser/devtools/client/shared/test/"; - var ACTOR_URL = TEST_URL_ROOT + "test-actor.js"; + const TEST_URL_ROOT = "http://example.com/browser/devtools/client/shared/test/"; + const ACTOR_URL = TEST_URL_ROOT + "test-actor.js"; -// Register a test actor that can operate on the remote document + // Register a test actor that can operate on the remote document exports.registerTestActor = Task.async(function* (client) { - // First, instanciate ActorRegistryFront to be able to dynamically - // register an actor + // First, instanciate ActorRegistryFront to be able to dynamically register an actor let deferred = defer(); client.listTabs(deferred.resolve); let response = yield deferred.promise; let { ActorRegistryFront } = require("devtools/shared/fronts/actor-registry"); let registryFront = ActorRegistryFront(client, response); - // Then ask to register our test-actor to retrieve its front + // Then ask to register our test-actor to retrieve its front let options = { type: { tab: true }, constructor: "TestActor", prefix: "testActor" }; let testActorFront = yield registryFront.registerActor(ACTOR_URL, options); return testActorFront; }); -// Load the test actor in a custom sandbox -// as we can't use SDK module loader with URIs - var loadFront = Task.async(function* () { + // Load the test actor in a custom sandbox as we can't use SDK module loader with URIs + let loadFront = Task.async(function* () { let sourceText = yield request(ACTOR_URL); const principal = CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(); const sandbox = Cu.Sandbox(principal); - const exports = sandbox.exports = {}; + sandbox.exports = {}; sandbox.require = require; Cu.evalInSandbox(sourceText, sandbox, "1.8", ACTOR_URL, 1); return sandbox.exports; }); -// Ensure fetching a live TabActor form for the targeted app -// (helps fetching the test actor registered dynamically) - var getUpdatedForm = function (client, tab) { + // Ensure fetching a live TabActor form for the targeted app + // (helps fetching the test actor registered dynamically) + let getUpdatedForm = function (client, tab) { return client.getTab({tab: tab}) - .then(response => response.tab); + .then(response => response.tab); }; -// Spawn an instance of the test actor for the given toolbox + // Spawn an instance of the test actor for the given toolbox exports.getTestActor = Task.async(function* (toolbox) { let client = toolbox.target.client; return getTestActor(client, toolbox.target.tab, toolbox); }); - -// Sometimes, we need the test actor before opening or without a toolbox -// then just create a front for the given `tab` + // Sometimes, we need the test actor before opening or without a toolbox then just + // create a front for the given `tab` exports.getTestActorWithoutToolbox = Task.async(function* (tab) { let { DebuggerServer } = require("devtools/server/main"); let { DebuggerClient } = require("devtools/shared/client/main"); - // We need to spawn a client instance, - // but for that we have to first ensure a server is running + // We need to spawn a client instance, + // but for that we have to first ensure a server is running if (!DebuggerServer.initialized) { DebuggerServer.init(); DebuggerServer.addBrowserActors(); } let client = new DebuggerClient(DebuggerServer.connectPipe()); yield client.connect(); - // We also need to make sure the test actor is registered on the server. - yield registerTestActor(client); + // We also need to make sure the test actor is registered on the server. + yield exports.registerTestActor(client); return getTestActor(client, tab); }); -// Fetch the content of a URI - var request = function (uri) { + // Fetch the content of a URI + let request = function (uri) { return fetch(uri).then(({ content }) => content); }; - var getTestActor = Task.async(function* (client, tab, toolbox) { - // We may have to update the form in order to get the dynamically registered - // test actor. + let getTestActor = Task.async(function* (client, tab, toolbox) { + // We may have to update the form in order to get the dynamically registered + // test actor. let form = yield getUpdatedForm(client, tab); let { TestActorFront } = yield loadFront(); return new TestActorFront(client, form, toolbox); }); - })(this);
--- a/devtools/client/storage/test/head.js +++ b/devtools/client/storage/test/head.js @@ -79,16 +79,27 @@ function* openTabAndSetupStorage(url) { }; _getAllWindows(baseWindow); return windows; } let windows = getAllWindows(content); for (let win of windows) { + let readyState = win.document.readyState; + info(`Found a window: ${readyState}`); + if (readyState != "complete") { + yield new Promise(resolve => { + let onLoad = () => { + win.removeEventListener("load", onLoad); + resolve(); + }; + win.addEventListener("load", onLoad); + }); + } if (win.setup) { yield win.setup(); } } }); // open storage inspector return yield openStoragePanel();
--- a/devtools/client/storage/test/storage-listings.html +++ b/devtools/client/storage/test/storage-listings.html @@ -21,17 +21,17 @@ document.cookie = "c1=foobar; expires=" document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname; document.cookie = "c3=foobar-2; secure=true; expires=" + new Date(cookieExpiresTime2).toGMTString() + "; path=/"; // ... and some local storage items .. localStorage.setItem("ls1", "foobar"); localStorage.setItem("ls2", "foobar-2"); // ... and finally some session storage items too sessionStorage.setItem("ss1", "foobar-3"); -dump("added cookies and stuff from main page\n"); +dump("added cookies and storage from main page\n"); let idbGenerator = function*() { let request = indexedDB.open("idb1", 1); request.onerror = function() { throw new Error("error opening db connection"); }; let db = yield new Promise(done => { request.onupgradeneeded = event => { @@ -82,17 +82,17 @@ let idbGenerator = function*() { }; }); // Prevents AbortError during close() yield new Promise(done => { request.onsuccess = done; }); db2.close(); - dump("added cookies and stuff from main page\n"); + dump("added indexedDB from main page\n"); }; function deleteDB(dbName) { return new Promise(resolve => { dump("removing database " + dbName + " from " + document.location + "\n"); indexedDB.deleteDatabase(dbName).onsuccess = resolve; }); }
--- a/devtools/client/storage/test/storage-secured-iframe.html +++ b/devtools/client/storage/test/storage-secured-iframe.html @@ -7,16 +7,17 @@ Iframe for testing multiple host detetio <meta charset="utf-8"> </head> <body> <script type="application/javascript;version=1.7"> "use strict"; document.cookie = "sc1=foobar;"; localStorage.setItem("iframe-s-ls1", "foobar"); sessionStorage.setItem("iframe-s-ss1", "foobar-2"); +dump("added cookies and storage from secured iframe\n"); let idbGenerator = function*() { let request = indexedDB.open("idb-s1", 1); request.onerror = function() { throw new Error("error opening db connection"); }; let db = yield new Promise(done => { request.onupgradeneeded = event => { @@ -60,17 +61,17 @@ let idbGenerator = function*() { transaction = db2.transaction(["obj-s2"], "readwrite"); let store3 = transaction.objectStore("obj-s2"); store3.add({id3: 16, name2: "foo", email: "foo@bar.com"}); yield new Promise(success => { transaction.oncomplete = success; }); db2.close(); - dump("added cookies and stuff from secured iframe\n"); + dump("added indexedDB from secured iframe\n"); }; function deleteDB(dbName) { return new Promise(resolve => { dump("removing database " + dbName + " from " + document.location + "\n"); indexedDB.deleteDatabase(dbName).onsuccess = resolve; }); }
--- a/devtools/client/storage/test/storage-unsecured-iframe.html +++ b/devtools/client/storage/test/storage-unsecured-iframe.html @@ -8,12 +8,12 @@ Iframe for testing multiple host detetio </head> <body> <script> "use strict"; document.cookie = "uc1=foobar; domain=.example.org; path=/; secure=true"; localStorage.setItem("iframe-u-ls1", "foobar"); sessionStorage.setItem("iframe-u-ss1", "foobar1"); sessionStorage.setItem("iframe-u-ss2", "foobar2"); -console.log("added cookies and stuff from unsecured iframe"); +dump("added cookies and storage from unsecured iframe\n"); </script> </body> </html>
--- a/devtools/client/styleeditor/test/browser.ini +++ b/devtools/client/styleeditor/test/browser.ini @@ -49,16 +49,17 @@ support-files = doc_uncached.css doc_uncached.html doc_xulpage.xul sync.html !/devtools/client/commandline/test/helpers.js !/devtools/client/framework/test/shared-head.js !/devtools/client/inspector/shared/test/head.js !/devtools/client/inspector/test/head.js + !/devtools/client/inspector/test/shared-head.js !/devtools/client/shared/test/test-actor-registry.js !/devtools/client/shared/test/test-actor.js [browser_styleeditor_autocomplete.js] [browser_styleeditor_autocomplete-disabled.js] [browser_styleeditor_bug_740541_iframes.js] [browser_styleeditor_bug_851132_middle_click.js] [browser_styleeditor_bug_870339.js]
--- a/devtools/client/webaudioeditor/controller.js +++ b/devtools/client/webaudioeditor/controller.js @@ -64,16 +64,22 @@ var WebAudioEditorController = { // If not, get the JSON directly. Using the actor method is preferable so the client // knows exactly what methods are supported on the server. let actorHasDefinition = yield gTarget.actorHasMethod("webaudio", "getDefinition"); if (actorHasDefinition) { AUDIO_NODE_DEFINITION = yield gFront.getDefinition(); } else { AUDIO_NODE_DEFINITION = require("devtools/server/actors/utils/audionodes.json"); } + + // Make sure the backend is prepared to handle audio contexts. + // Since actors are created lazily on the first request to them, we need to send an + // early request to ensure the CallWatcherActor is running and watching for new window + // globals. + gFront.setup({ reload: false }); }), /** * Remove events emitted by the current tab target. */ destroy: function () { gTarget.off("will-navigate", this._onTabNavigated); gTarget.off("navigate", this._onTabNavigated); @@ -128,21 +134,16 @@ var WebAudioEditorController = { }, /** * Called for each location change in the debugged tab. */ _onTabNavigated: Task.async(function* (event, {isFrameSwitching}) { switch (event) { case "will-navigate": { - // Make sure the backend is prepared to handle audio contexts. - if (!isFrameSwitching) { - yield gFront.setup({ reload: false }); - } - // Clear out current UI. this.reset(); // When switching to an iframe, ensure displaying the reload button. // As the document has already been loaded without being hooked. if (isFrameSwitching) { $("#reload-notice").hidden = false; $("#waiting-notice").hidden = true;
--- a/devtools/client/webaudioeditor/test/browser_audionode-actor-connectnode-disconnect.js +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-connectnode-disconnect.js @@ -6,23 +6,22 @@ * Uses the editor front as the actors do not retain connect state. */ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); - + reload(target); + let [actors] = yield events; let [dest, osc, gain] = actors; info("Disconnecting oscillator..."); osc.disconnect(); yield Promise.all([ waitForGraphRendered(panelWin, 3, 1), once(gAudioNodes, "disconnect") ]);
--- a/devtools/client/webaudioeditor/test/browser_audionode-actor-connectparam.js +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-connectparam.js @@ -6,23 +6,22 @@ * Uses the editor front as the actors do not retain connect state. */ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); - + reload(target); + let [actors] = yield events; let [dest, osc, gain] = actors; yield osc.disconnect(); osc.connectParam(gain, "gain"); yield Promise.all([ waitForGraphRendered(panelWin, 3, 1, 1), once(gAudioNodes, "connect")
--- a/devtools/client/webaudioeditor/test/browser_callwatcher-01.js +++ b/devtools/client/webaudioeditor/test/browser_callwatcher-01.js @@ -10,17 +10,17 @@ const BUG_1130901_URL = EXAMPLE_URL + "doc_bug_1130901.html"; add_task(function* () { let { target, panel } = yield initWebAudioEditor(BUG_1130901_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; + let rendered = waitForGraphRendered(panelWin, 3, 0); reload(target); - - yield waitForGraphRendered(panelWin, 3, 0); + yield rendered; ok(true, "Successfully created a node from AudioContext via `call`."); ok(true, "Successfully created a node from AudioContext via `apply`."); yield teardown(target); });
--- a/devtools/client/webaudioeditor/test/browser_callwatcher-02.js +++ b/devtools/client/webaudioeditor/test/browser_callwatcher-02.js @@ -11,19 +11,19 @@ const BUG_1112378_URL = EXAMPLE_URL + "d add_task(function* () { let { target, panel } = yield initWebAudioEditor(BUG_1112378_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; loadFrameScripts(); + let rendered = waitForGraphRendered(panelWin, 2, 0); reload(target); - - yield waitForGraphRendered(panelWin, 2, 0); + yield rendered; let error = yield evalInDebuggee("throwError()"); is(error.lineNumber, 21, "error has correct lineNumber"); is(error.columnNumber, 11, "error has correct columnNumber"); is(error.name, "TypeError", "error has correct name"); is(error.message, "Argument 1 is not valid for any of the 2-argument overloads of AudioNode.connect.", "error has correct message"); is(error.stringified, "TypeError: Argument 1 is not valid for any of the 2-argument overloads of AudioNode.connect.", "error is stringified correctly"); is(error.instanceof, true, "error is correctly an instanceof TypeError");
--- a/devtools/client/webaudioeditor/test/browser_wa_automation-view-01.js +++ b/devtools/client/webaudioeditor/test/browser_wa_automation-view-01.js @@ -8,23 +8,24 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(AUTOMATION_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS } = panelWin; let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + let [actors] = yield events; let nodeIds = actors.map(actor => actor.actorID); + let $tabbox = $("#web-audio-editor-tabs"); // Oscillator node click(panelWin, findGraphNode(panelWin, nodeIds[1])); yield waitForInspectorRender(panelWin, EVENTS); $tabbox.selectedIndex = 1; ok(isVisible($("#automation-graph-container")), "graph container should be visible");
--- a/devtools/client/webaudioeditor/test/browser_wa_automation-view-02.js +++ b/devtools/client/webaudioeditor/test/browser_wa_automation-view-02.js @@ -8,25 +8,24 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(AUTOMATION_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, AutomationView } = panelWin; let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + let [actors] = yield events; let nodeIds = actors.map(actor => actor.actorID); - // Oscillator node click(panelWin, findGraphNode(panelWin, nodeIds[1])); yield waitForInspectorRender(panelWin, EVENTS); click(panelWin, $("#automation-tab")); ok(AutomationView._selectedParamName, "frequency", "AutomatioView is set on 'frequency'"); ok($(".automation-param-button[data-param='frequency']").getAttribute("selected"),
--- a/devtools/client/webaudioeditor/test/browser_wa_controller-01.js +++ b/devtools/client/webaudioeditor/test/browser_wa_controller-01.js @@ -9,20 +9,20 @@ const BUG_1125817_URL = EXAMPLE_URL + "doc_bug_1125817.html"; add_task(function* () { let { target, panel } = yield initWebAudioEditor(BUG_1125817_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ once(gAudioNodes, "add", 2), once(gAudioNodes, "disconnect"), waitForGraphRendered(panelWin, 2, 0) ]); + reload(target); + yield events; ok(true, "Successfully disconnected a just-created node."); yield teardown(target); });
--- a/devtools/client/webaudioeditor/test/browser_wa_destroy-node-01.js +++ b/devtools/client/webaudioeditor/test/browser_wa_destroy-node-01.js @@ -11,22 +11,22 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(DESTROY_NODES_URL); let { panelWin } = panel; let { gFront, $, $$, gAudioNodes } = panelWin; let started = once(gFront, "start-context"); - reload(target); - - let [created] = yield Promise.all([ + let events = Promise.all([ getNSpread(gAudioNodes, "add", 13), waitForGraphRendered(panelWin, 13, 2) ]); + reload(target); + let [created] = yield events; // Flatten arrays of event arguments and take the first (AudioNodeModel) // and get its ID. let actorIDs = created.map(ev => ev[0].id); // Click a soon-to-be dead buffer node yield clickGraphNode(panelWin, actorIDs[5]);
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-click.js +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-click.js @@ -8,23 +8,22 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL); let panelWin = panel.panelWin; let { gFront, $, $$, InspectorView } = panelWin; let started = once(gFront, "start-context"); - reload(target); - - let [actors, _] = yield Promise.all([ + let events = Promise.all([ getN(gFront, "create-node", 8), waitForGraphRendered(panel.panelWin, 8, 8) ]); - + reload(target); + let [actors, _] = yield events; let nodeIds = actors.map(actor => actor.actorID); ok(!InspectorView.isVisible(), "InspectorView hidden on start."); yield clickGraphNode(panelWin, nodeIds[1], true); ok(InspectorView.isVisible(), "InspectorView visible after selecting a node."); is(InspectorView.getCurrentAudioNode().id, nodeIds[1], "InspectorView has correct node set.");
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-markers.js +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-markers.js @@ -14,22 +14,22 @@ add_task(function* () { let currentTheme = Services.prefs.getCharPref("devtools.theme"); ok(MARKER_STYLING.light, "Marker styling exists for light theme."); ok(MARKER_STYLING.dark, "Marker styling exists for dark theme."); let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + let [actors] = yield events; is(getFill($("#arrowhead")), MARKER_STYLING[currentTheme], "marker initially matches theme."); // Switch to light setTheme("light"); is(getFill($("#arrowhead")), MARKER_STYLING.light, "marker styling matches light theme on change.");
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-render-01.js +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-01.js @@ -9,25 +9,24 @@ var connectCount = 0; add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; let started = once(gFront, "start-context"); - reload(target); - gAudioNodes.on("connect", onConnectNode); - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); - + reload(target); + let [actors] = yield events; let [destId, oscId, gainId] = actors.map(actor => actor.actorID); ok(findGraphNode(panelWin, oscId).classList.contains("type-OscillatorNode"), "found OscillatorNode with class"); ok(findGraphNode(panelWin, gainId).classList.contains("type-GainNode"), "found GainNode with class"); ok(findGraphNode(panelWin, destId).classList.contains("type-AudioDestinationNode"), "found AudioDestinationNode with class"); is(findGraphEdge(panelWin, oscId, gainId).toString(), "[object SVGGElement]", "found edge for osc -> gain"); is(findGraphEdge(panelWin, gainId, destId).toString(), "[object SVGGElement]", "found edge for gain -> dest");
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-render-02.js +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-02.js @@ -7,23 +7,22 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$ } = panelWin; let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ getN(gFront, "create-node", 8), waitForGraphRendered(panelWin, 8, 8) ]); - + reload(target); + let [actors] = yield events; let nodeIDs = actors.map(actor => actor.actorID); let types = ["AudioDestinationNode", "OscillatorNode", "GainNode", "ScriptProcessorNode", "OscillatorNode", "GainNode", "AudioBufferSourceNode", "BiquadFilterNode"]; types.forEach((type, i) => { ok(findGraphNode(panelWin, nodeIDs[i]).classList.contains("type-" + type), "found " + type + " with class");
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-render-03.js +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-03.js @@ -5,23 +5,22 @@ * Tests to ensure that selected nodes stay selected on graph redraw. */ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS } = panelWin; - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ getN(gFront, "create-node", 3), waitForGraphRendered(panelWin, 3, 2) ]); - + reload(target); + let [actors] = yield events; let [dest, osc, gain] = actors; yield clickGraphNode(panelWin, gain.actorID); ok(findGraphNode(panelWin, gain.actorID).classList.contains("selected"), "Node selected once."); // Disconnect a node to trigger a rerender osc.disconnect();
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-render-04.js +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-04.js @@ -7,23 +7,22 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(CONNECT_MULTI_PARAM_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS } = panelWin; let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ getN(gFront, "create-node", 5), waitForGraphRendered(panelWin, 5, 2, 3) ]); - + reload(target); + let [actors] = yield events; let nodeIDs = actors.map(actor => actor.actorID); let [, carrier, gain, mod1, mod2] = nodeIDs; let edges = [ [mod1, gain, "gain", "mod1 -> gain[gain]"], [mod2, carrier, "frequency", "mod2 -> carrier[frequency]"], [mod2, carrier, "detune", "mod2 -> carrier[detune]"]
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-render-05.js +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-05.js @@ -5,23 +5,22 @@ * Tests to ensure that param connections trigger graph redraws */ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS } = panelWin; - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ getN(gFront, "create-node", 3), waitForGraphRendered(panelWin, 3, 2, 0) ]); - + reload(target); + let [actors] = yield events; let [dest, osc, gain] = actors; yield osc.disconnect(); osc.connectParam(gain, "gain"); yield waitForGraphRendered(panelWin, 3, 1, 1); ok(true, "Graph re-rendered upon param connection");
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-render-06.js +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-06.js @@ -7,19 +7,19 @@ const BUG_1141261_URL = EXAMPLE_URL + "doc_bug_1141261.html"; add_task(function* () { let { target, panel } = yield initWebAudioEditor(BUG_1141261_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS } = panelWin; - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ getN(gFront, "create-node", 3), waitForGraphRendered(panelWin, 3, 1, 0) ]); + reload(target); + yield events; ok(true, "Graph correctly shows gain node as disconnected"); yield teardown(target); });
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-selected.js +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-selected.js @@ -7,23 +7,22 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS } = panelWin; let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); - + reload(target); + let [actors] = yield events; let [destId, oscId, gainId] = actors.map(actor => actor.actorID); ok(!findGraphNode(panelWin, destId).classList.contains("selected"), "No nodes selected on start. (destination)"); ok(!findGraphNode(panelWin, oscId).classList.contains("selected"), "No nodes selected on start. (oscillator)"); ok(!findGraphNode(panelWin, gainId).classList.contains("selected"), "No nodes selected on start. (gain)");
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-zoom.js +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-zoom.js @@ -8,36 +8,36 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, ContextView } = panelWin; let started = once(gFront, "start-context"); yield Promise.all([ + waitForGraphRendered(panelWin, 3, 2), reload(target), - waitForGraphRendered(panelWin, 3, 2) ]); is(ContextView.getCurrentScale(), 1, "Default graph scale is 1."); is(ContextView.getCurrentTranslation()[0], 20, "Default x-translation is 20."); is(ContextView.getCurrentTranslation()[1], 20, "Default y-translation is 20."); // Change both attribute and D3's internal store panelWin.d3.select("#graph-target").attr("transform", "translate([100, 400]) scale(10)"); ContextView._zoomBinding.scale(10); ContextView._zoomBinding.translate([100, 400]); is(ContextView.getCurrentScale(), 10, "After zoom, scale is 10."); is(ContextView.getCurrentTranslation()[0], 100, "After zoom, x-translation is 100."); is(ContextView.getCurrentTranslation()[1], 400, "After zoom, y-translation is 400."); yield Promise.all([ + waitForGraphRendered(panelWin, 3, 2), reload(target), - waitForGraphRendered(panelWin, 3, 2) ]); is(ContextView.getCurrentScale(), 1, "After refresh, graph scale is 1."); is(ContextView.getCurrentTranslation()[0], 20, "After refresh, x-translation is 20."); is(ContextView.getCurrentTranslation()[1], 20, "After refresh, y-translation is 20."); yield teardown(target); });
--- a/devtools/client/webaudioeditor/test/browser_wa_inspector-bypass-01.js +++ b/devtools/client/webaudioeditor/test/browser_wa_inspector-bypass-01.js @@ -5,22 +5,22 @@ * Tests that nodes are correctly bypassed when bypassing. */ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + let [actors] = yield events; let nodeIds = actors.map(actor => actor.actorID); // Wait for the node to be set as well as the inspector to come fully into the view yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]), true); let $bypass = $("toolbarbutton.bypass"); is((yield actors[1].isBypassed()), false, "AudioNodeActor is not bypassed by default.");
--- a/devtools/client/webaudioeditor/test/browser_wa_inspector-toggle.js +++ b/devtools/client/webaudioeditor/test/browser_wa_inspector-toggle.js @@ -9,22 +9,22 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, InspectorView } = panelWin; let gVars = InspectorView._propsView; let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + let [actors] = yield events; let nodeIds = actors.map(actor => actor.actorID); ok(!InspectorView.isVisible(), "InspectorView hidden on start."); // Open inspector pane $("#inspector-pane-toggle").click(); yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED);
--- a/devtools/client/webaudioeditor/test/browser_wa_inspector-width.js +++ b/devtools/client/webaudioeditor/test/browser_wa_inspector-width.js @@ -9,41 +9,42 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, InspectorView } = panelWin; let gVars = InspectorView._propsView; let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + let [actors] = yield events; let nodeIds = actors.map(actor => actor.actorID); ok(!InspectorView.isVisible(), "InspectorView hidden on start."); // Open inspector pane $("#inspector-pane-toggle").click(); yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED); let newInspectorWidth = 500; // Setting width to new_inspector_width $("#web-audio-inspector").setAttribute("width", newInspectorWidth); - reload(target); // Width should be 500 after reloading - [actors] = yield Promise.all([ + events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + [actors] = yield events; nodeIds = actors.map(actor => actor.actorID); // Open inspector pane $("#inspector-pane-toggle").click(); yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED); yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]));
--- a/devtools/client/webaudioeditor/test/browser_wa_inspector.js +++ b/devtools/client/webaudioeditor/test/browser_wa_inspector.js @@ -9,22 +9,22 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, InspectorView } = panelWin; let gVars = InspectorView._propsView; let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + let [actors] = yield events; let nodeIds = actors.map(actor => actor.actorID); ok(!InspectorView.isVisible(), "InspectorView hidden on start."); ok(isVisible($("#web-audio-editor-details-pane-empty")), "InspectorView empty message should show when no node's selected."); ok(!isVisible($("#web-audio-editor-tabs")), "InspectorView tabs view should be hidden when no node's selected.");
--- a/devtools/client/webaudioeditor/test/browser_wa_navigate.js +++ b/devtools/client/webaudioeditor/test/browser_wa_navigate.js @@ -6,33 +6,33 @@ * the audio graph if both pages have an AudioContext. */ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $ } = panelWin; - reload(target); - - var [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + yield events; var { nodes, edges } = countGraphObjects(panelWin); is(nodes, 3, "should only be 3 nodes."); is(edges, 2, "should only be 2 edges."); - navigate(target, SIMPLE_NODES_URL); - - var [actors] = yield Promise.all([ + events = Promise.all([ getN(gFront, "create-node", 15), waitForGraphRendered(panelWin, 15, 0) ]); + navigate(target, SIMPLE_NODES_URL); + yield events; is($("#reload-notice").hidden, true, "The 'reload this page' notice should be hidden after context found after navigation."); is($("#waiting-notice").hidden, true, "The 'waiting for an audio context' notice should be hidden after context found after navigation."); is($("#content").hidden, false, "The tool's content should reappear without closing and reopening the toolbox.");
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-01.js +++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-01.js @@ -8,22 +8,22 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, PropertiesView } = panelWin; let gVars = PropertiesView._propsView; let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + let [actors] = yield events; let nodeIds = actors.map(actor => actor.actorID); click(panelWin, findGraphNode(panelWin, nodeIds[1])); // Wait for the node to be set as well as the inspector to come fully into the view yield Promise.all([ waitForInspectorRender(panelWin, EVENTS), once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED) ]);
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-02.js +++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-02.js @@ -8,22 +8,22 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, PropertiesView } = panelWin; let gVars = PropertiesView._propsView; let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ getN(gFront, "create-node", 8), waitForGraphRendered(panelWin, 8, 8) ]); + reload(target); + let [actors] = yield events; let nodeIds = actors.map(actor => actor.actorID); click(panelWin, findGraphNode(panelWin, nodeIds[3])); // Wait for the node to be set as well as the inspector to come fully into the view yield Promise.all([ waitForInspectorRender(panelWin, EVENTS), once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED), ]);
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view-media-nodes.js +++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-media-nodes.js @@ -39,26 +39,26 @@ add_task(function* () { let { panelWin } = panel; let { gFront, $, $$, EVENTS, PropertiesView } = panelWin; let gVars = PropertiesView._propsView; // Auto enable getUserMedia let mediaPermissionPref = Services.prefs.getBoolPref(MEDIA_PERMISSION); Services.prefs.setBoolPref(MEDIA_PERMISSION, true); - reload(target); - yield loadFrameScripts(); - let [actors] = yield Promise.all([ + let events = Promise.all([ getN(gFront, "create-node", 4), waitForGraphRendered(panelWin, 4, 0) ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); - let nodeIds = actors.map(actor => actor.actorID); let types = [ "AudioDestinationNode", "MediaElementAudioSourceNode", "MediaStreamAudioSourceNode", "MediaStreamAudioDestinationNode" ]; let defaults = yield Promise.all(types.map(type => nodeDefaultValues(type))); for (let i = 0; i < types.length; i++) {
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view-params-objects.js +++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-params-objects.js @@ -9,22 +9,22 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(BUFFER_AND_ARRAY_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, PropertiesView } = panelWin; let gVars = PropertiesView._propsView; let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ getN(gFront, "create-node", 3), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + let [actors] = yield events; let nodeIds = actors.map(actor => actor.actorID); click(panelWin, findGraphNode(panelWin, nodeIds[2])); yield waitForInspectorRender(panelWin, EVENTS); checkVariableView(gVars, 0, { "curve": "Float32Array" }, "WaveShaper's `curve` is listed as an `Float32Array`.");
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view-params.js +++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-params.js @@ -9,25 +9,26 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_NODES_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, PropertiesView } = panelWin; let gVars = PropertiesView._propsView; let started = once(gFront, "start-context"); - reload(target); - yield loadFrameScripts(); - let [actors] = yield Promise.all([ + let events = Promise.all([ getN(gFront, "create-node", 15), waitForGraphRendered(panelWin, 15, 0) ]); + reload(target); + let [actors] = yield events; let nodeIds = actors.map(actor => actor.actorID); + let types = [ "AudioDestinationNode", "AudioBufferSourceNode", "ScriptProcessorNode", "AnalyserNode", "GainNode", "DelayNode", "BiquadFilterNode", "WaveShaperNode", "PannerNode", "ConvolverNode", "ChannelSplitterNode", "ChannelMergerNode", "DynamicsCompressorNode", "OscillatorNode" ]; let defaults = yield Promise.all(types.map(type => nodeDefaultValues(type)));
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view.js +++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view.js @@ -8,22 +8,22 @@ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, $$, EVENTS, PropertiesView } = panelWin; let gVars = PropertiesView._propsView; let started = once(gFront, "start-context"); - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + let [actors] = yield events; let nodeIds = actors.map(actor => actor.actorID); // Gain node click(panelWin, findGraphNode(panelWin, nodeIds[2])); yield waitForInspectorRender(panelWin, EVENTS); ok(isVisible($("#properties-content")), "Parameters shown when they exist."); ok(!isVisible($("#properties-empty")),
--- a/devtools/client/webaudioeditor/test/browser_wa_reset-02.js +++ b/devtools/client/webaudioeditor/test/browser_wa_reset-02.js @@ -6,32 +6,32 @@ * the graph. */ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $ } = panelWin; - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + yield events; let { nodes, edges } = countGraphObjects(panelWin); is(nodes, 3, "should only be 3 nodes."); is(edges, 2, "should only be 2 edges."); - reload(target); - - [actors] = yield Promise.all([ + events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + yield events; ({ nodes, edges } = countGraphObjects(panelWin)); is(nodes, 3, "after reload, should only be 3 nodes."); is(edges, 2, "after reload, should only be 2 edges."); yield teardown(target); });
--- a/devtools/client/webaudioeditor/test/browser_wa_reset-03.js +++ b/devtools/client/webaudioeditor/test/browser_wa_reset-03.js @@ -6,38 +6,38 @@ * the inspector and selected node. */ add_task(function* () { let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); let { panelWin } = panel; let { gFront, $, InspectorView } = panelWin; - reload(target); - - let [actors] = yield Promise.all([ + let events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + let [actors] = yield events; let nodeIds = actors.map(actor => actor.actorID); yield clickGraphNode(panelWin, nodeIds[1], true); ok(InspectorView.isVisible(), "InspectorView visible after selecting a node."); is(InspectorView.getCurrentAudioNode().id, nodeIds[1], "InspectorView has correct node set."); /** * Reload */ - reload(target); - - [actors] = yield Promise.all([ + events = Promise.all([ get3(gFront, "create-node"), waitForGraphRendered(panelWin, 3, 2) ]); + reload(target); + [actors] = yield events; nodeIds = actors.map(actor => actor.actorID); ok(!InspectorView.isVisible(), "InspectorView hidden on start."); is(InspectorView.getCurrentAudioNode(), null, "InspectorView has no current node set on reset."); yield clickGraphNode(panelWin, nodeIds[2], true); ok(InspectorView.isVisible(),
--- a/devtools/client/webaudioeditor/test/head.js +++ b/devtools/client/webaudioeditor/test/head.js @@ -203,21 +203,23 @@ function teardown(aTarget) { // us to listen for `n` events and return a promise resolving to them. // // Takes a `front` object that is an event emitter, the number of // programs that should be listened to and waited on, and an optional // `onAdd` function that calls with the entire actors array on program link function getN(front, eventName, count, spread) { let actors = []; let deferred = Promise.defer(); + info(`Waiting for ${count} ${eventName} events`); front.on(eventName, function onEvent(...args) { let actor = args[0]; if (actors.length !== count) { actors.push(spread ? args : actor); } + info(`Got ${actors.length} / ${count} ${eventName} events`); if (actors.length === count) { front.off(eventName, onEvent); deferred.resolve(actors); } }); return deferred.promise; } @@ -232,18 +234,21 @@ function getNSpread(front, eventName, co /** * Waits for the UI_GRAPH_RENDERED event to fire, but only * resolves when the graph was rendered with the correct count of * nodes and edges. */ function waitForGraphRendered(front, nodeCount, edgeCount, paramEdgeCount) { let deferred = Promise.defer(); let eventName = front.EVENTS.UI_GRAPH_RENDERED; + info(`Wait for graph rendered with ${nodeCount} nodes, ${edgeCount} edges`); front.on(eventName, function onGraphRendered(_, nodes, edges, pEdges) { let paramEdgesDone = paramEdgeCount != null ? paramEdgeCount === pEdges : true; + info(`Got graph rendered with ${nodes} / ${nodeCount} nodes, ` + + `${edges} / ${edgeCount} edges`); if (nodes === nodeCount && edges === edgeCount && paramEdgesDone) { front.off(eventName, onGraphRendered); deferred.resolve(); } }); return deferred.promise; }
--- a/devtools/client/webconsole/test/browser.ini +++ b/devtools/client/webconsole/test/browser.ini @@ -262,17 +262,17 @@ skip-if = e10s # Bug 1042253 - webconsol [browser_webconsole_bug_632817.js] [browser_webconsole_bug_642108_pruneTest.js] [browser_webconsole_autocomplete_and_selfxss.js] subsuite = clipboard [browser_webconsole_bug_644419_log_limits.js] [browser_webconsole_bug_646025_console_file_location.js] [browser_webconsole_bug_651501_document_body_autocomplete.js] [browser_webconsole_bug_653531_highlighter_console_helper.js] -skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s +skip-if = true # Requires direct access to content nodes [browser_webconsole_bug_658368_time_methods.js] [browser_webconsole_bug_659907_console_dir.js] [browser_webconsole_bug_660806_history_nav.js] [browser_webconsole_bug_664131_console_group.js] [browser_webconsole_bug_686937_autocomplete_JSTerm_helpers.js] [browser_webconsole_bug_704295.js] [browser_webconsole_bug_734061_No_input_change_and_Tab_key_pressed.js] [browser_webconsole_bug_737873_mixedcontent.js]
--- a/devtools/client/webide/test/browser_tabs.js +++ b/devtools/client/webide/test/browser_tabs.js @@ -4,18 +4,16 @@ const TEST_URI = "http://example.com/browser/devtools/client/webide/test/doc_tabs.html"; function test() { waitForExplicitFinish(); requestCompleteLog(); Task.spawn(function* () { - const { DebuggerServer } = require("devtools/server/main"); - // Since we test the connections set below, destroy the server in case it // was left open. DebuggerServer.destroy(); DebuggerServer.init(); DebuggerServer.addBrowserActors(); let tab = yield addTab(TEST_URI);
--- a/devtools/client/webide/test/head.js +++ b/devtools/client/webide/test/head.js @@ -1,23 +1,24 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; var {utils: Cu, classes: Cc, interfaces: Ci} = Components; -const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); -const {FileUtils} = require("resource://gre/modules/FileUtils.jsm"); -const {gDevTools} = require("devtools/client/framework/devtools"); +const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); +const { FileUtils } = require("resource://gre/modules/FileUtils.jsm"); +const { gDevTools } = require("devtools/client/framework/devtools"); const promise = require("promise"); const Services = require("Services"); -const {Task} = require("devtools/shared/task"); -const {AppProjects} = require("devtools/client/webide/modules/app-projects"); +const { Task } = require("devtools/shared/task"); +const { AppProjects } = require("devtools/client/webide/modules/app-projects"); const DevToolsUtils = require("devtools/shared/DevToolsUtils"); +const { DebuggerServer } = require("devtools/server/main"); DevToolsUtils.testing = true; var TEST_BASE; if (window.location === "chrome://browser/content/browser.xul") { TEST_BASE = "chrome://mochitests/content/browser/devtools/client/webide/test/"; } else { TEST_BASE = "chrome://mochitests/content/chrome/devtools/client/webide/test/"; } @@ -40,38 +41,37 @@ registerCleanupFunction(() => { Services.prefs.clearUserPref("devtools.webide.enableLocalRuntime"); Services.prefs.clearUserPref("devtools.webide.autoinstallADBHelper"); Services.prefs.clearUserPref("devtools.webide.autoinstallFxdtAdapters"); Services.prefs.clearUserPref("devtools.webide.busyTimeout"); Services.prefs.clearUserPref("devtools.webide.lastSelectedProject"); Services.prefs.clearUserPref("devtools.webide.lastConnectedRuntime"); }); -function openWebIDE(autoInstallAddons) { +var openWebIDE = Task.async(function* (autoInstallAddons) { info("opening WebIDE"); Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", !!autoInstallAddons); Services.prefs.setBoolPref("devtools.webide.autoinstallFxdtAdapters", !!autoInstallAddons); - let deferred = promise.defer(); - let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher); let win = ww.openWindow(null, "chrome://webide/content/", "webide", "chrome,centerscreen,resizable", null); - win.addEventListener("load", function onLoad() { - win.removeEventListener("load", onLoad); - info("WebIDE open"); - SimpleTest.requestCompleteLog(); - SimpleTest.executeSoon(() => { - deferred.resolve(win); + yield new Promise(resolve => { + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad); + SimpleTest.requestCompleteLog(); + SimpleTest.executeSoon(resolve); }); }); - return deferred.promise; -} + info("WebIDE open"); + + return win; +}); function closeWebIDE(win) { info("Closing WebIDE"); let deferred = promise.defer(); Services.prefs.clearUserPref("devtools.webide.widget.enabled"); @@ -225,8 +225,24 @@ function connectToLocalRuntime(win) { items[1].click(); return updated; } function handleError(aError) { ok(false, "Got an error: " + aError.message + "\n" + aError.stack); finish(); } + +function waitForConnectionChange(expectedState, count = 1) { + return new Promise(resolve => { + let onConnectionChange = (_, state) => { + if (state != expectedState) { + return; + } + if (--count != 0) { + return; + } + DebuggerServer.off("connectionchange", onConnectionChange); + resolve(); + }; + DebuggerServer.on("connectionchange", onConnectionChange); + }); +}
--- a/devtools/client/webide/test/test_autoconnect_runtime.html +++ b/devtools/client/webide/test/test_autoconnect_runtime.html @@ -14,18 +14,16 @@ <body> <script type="application/javascript;version=1.8"> window.onload = function() { SimpleTest.waitForExplicitFinish(); Task.spawn(function*() { - const { DebuggerServer } = require("devtools/server/main"); - if (!DebuggerServer.initialized) { DebuggerServer.init(); DebuggerServer.addBrowserActors(); } let win = yield openWebIDE(); let docRuntime = getRuntimeDocument(win); @@ -48,40 +46,44 @@ }; win.AppManager.runtimeList.usb.push(fakeRuntime); win.AppManager.update("runtime-list"); let panelNode = docRuntime.querySelector("#runtime-panel"); let items = panelNode.querySelectorAll(".runtime-panel-item-usb"); is(items.length, 1, "Found one runtime button"); - let deferred = promise.defer(); - win.AppManager.connection.once( - win.Connection.Events.CONNECTED, - () => deferred.resolve()); + let connectionsChanged = waitForConnectionChange("opened", 2); items[0].click(); ok(win.document.querySelector("window").className, "busy", "UI is busy"); yield win.UI._busyPromise; - is(Object.keys(DebuggerServer._connections).length, 1, "Connected"); + + yield connectionsChanged; + is(Object.keys(DebuggerServer._connections).length, 2, "Connected"); + + connectionsChanged = waitForConnectionChange("closed", 2); yield nextTick(); - yield closeWebIDE(win); + yield connectionsChanged; is(Object.keys(DebuggerServer._connections).length, 0, "Disconnected"); + connectionsChanged = waitForConnectionChange("opened", 2); + win = yield openWebIDE(); win.AppManager.runtimeList.usb.push(fakeRuntime); win.AppManager.update("runtime-list"); yield waitForUpdate(win, "runtime-targets"); - is(Object.keys(DebuggerServer._connections).length, 1, "Automatically reconnected"); + yield connectionsChanged; + is(Object.keys(DebuggerServer._connections).length, 2, "Automatically reconnected"); yield win.Cmds.disconnectRuntime(); yield closeWebIDE(win); DebuggerServer.destroy(); SimpleTest.finish();
--- a/devtools/client/webide/test/test_autoselect_project.html +++ b/devtools/client/webide/test/test_autoselect_project.html @@ -14,72 +14,78 @@ <body> <script type="application/javascript;version=1.8"> window.onload = function() { SimpleTest.waitForExplicitFinish(); Task.spawn(function* () { - const { DebuggerServer } = require("devtools/server/main"); - if (!DebuggerServer.initialized) { DebuggerServer.init(); DebuggerServer.addBrowserActors(); } let win = yield openWebIDE(); let docRuntime = getRuntimeDocument(win); let docProject = getProjectDocument(win); let panelNode = docRuntime.querySelector("#runtime-panel"); let items = panelNode.querySelectorAll(".runtime-panel-item-other"); is(items.length, 2, "Found 2 runtime buttons"); // Connect to local runtime + let connectionsChanged = waitForConnectionChange("opened", 2); items[1].click(); yield waitForUpdate(win, "runtime-targets"); - is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected"); + yield connectionsChanged; + is(Object.keys(DebuggerServer._connections).length, 2, "Locally connected"); ok(win.AppManager.isMainProcessDebuggable(), "Main process available"); // Select main process yield win.Cmds.showProjectPanel(); yield waitForUpdate(win, "runtime-targets"); SimpleTest.executeSoon(() => { docProject.querySelectorAll("#project-panel-runtimeapps .panel-item")[0].click(); }); yield waitForUpdate(win, "project"); let lastProject = Services.prefs.getCharPref("devtools.webide.lastSelectedProject"); is(lastProject, "mainProcess:", "Last project is main process"); + connectionsChanged = waitForConnectionChange("closed", 2); + yield nextTick(); yield closeWebIDE(win); + yield connectionsChanged; is(Object.keys(DebuggerServer._connections).length, 0, "Disconnected"); + connectionsChanged = waitForConnectionChange("opened", 2); + // Re-open, should reselect main process after connection win = yield openWebIDE(); docRuntime = getRuntimeDocument(win); panelNode = docRuntime.querySelector("#runtime-panel"); items = panelNode.querySelectorAll(".runtime-panel-item-other"); is(items.length, 2, "Found 2 runtime buttons"); // Connect to local runtime items[1].click(); yield waitForUpdate(win, "runtime-targets"); - is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected"); + yield connectionsChanged; + is(Object.keys(DebuggerServer._connections).length, 2, "Locally connected"); ok(win.AppManager.isMainProcessDebuggable(), "Main process available"); is(win.AppManager.selectedProject.type, "mainProcess", "Main process reselected"); // Wait for the toolbox to be fully loaded yield win.UI.toolboxPromise; // If we happen to pass a project object targeting the same context, // here, the main process, the `selectedProject` attribute shouldn't be updated
--- a/devtools/client/webide/test/test_device_permissions.html +++ b/devtools/client/webide/test/test_device_permissions.html @@ -14,18 +14,16 @@ <body> <script type="application/javascript;version=1.8"> window.onload = function() { SimpleTest.waitForExplicitFinish(); Task.spawn(function* () { - const { DebuggerServer } = require("devtools/server/main"); - if (!DebuggerServer.initialized) { DebuggerServer.init(); DebuggerServer.addBrowserActors(); } let win = yield openWebIDE(); let permIframe = win.document.querySelector("#deck-panel-permissionstable");
--- a/devtools/client/webide/test/test_device_preferences.html +++ b/devtools/client/webide/test/test_device_preferences.html @@ -14,18 +14,16 @@ <body> <script type="application/javascript;version=1.8"> window.onload = function() { SimpleTest.waitForExplicitFinish(); Task.spawn(function* () { - const { DebuggerServer } = require("devtools/server/main"); - if (!DebuggerServer.initialized) { DebuggerServer.init(); DebuggerServer.addBrowserActors(); } let win = yield openWebIDE(); let prefIframe = win.document.querySelector("#deck-panel-devicepreferences");
--- a/devtools/client/webide/test/test_device_runtime.html +++ b/devtools/client/webide/test/test_device_runtime.html @@ -14,18 +14,16 @@ <body> <script type="application/javascript;version=1.8"> window.onload = function() { SimpleTest.waitForExplicitFinish(); Task.spawn(function* () { - const { DebuggerServer } = require("devtools/server/main"); - if (!DebuggerServer.initialized) { DebuggerServer.init(); DebuggerServer.addBrowserActors(); } let win = yield openWebIDE(); let detailsIframe = win.document.querySelector("#deck-panel-runtimedetails");
--- a/devtools/client/webide/test/test_device_settings.html +++ b/devtools/client/webide/test/test_device_settings.html @@ -14,18 +14,16 @@ <body> <script type="application/javascript;version=1.8"> window.onload = function() { SimpleTest.waitForExplicitFinish(); Task.spawn(function*() { - const { DebuggerServer } = require("devtools/server/main"); - if (SpecialPowers.isMainProcess()) { Cu.import("resource://gre/modules/SettingsRequestManager.jsm"); } if (!DebuggerServer.initialized) { DebuggerServer.init(); DebuggerServer.addBrowserActors(); }
--- a/devtools/client/webide/test/test_fullscreenToolbox.html +++ b/devtools/client/webide/test/test_fullscreenToolbox.html @@ -23,17 +23,16 @@ docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click(); return deferred.promise; } window.onload = function() { SimpleTest.waitForExplicitFinish(); Task.spawn(function* () { - const { DebuggerServer } = require("devtools/server/main"); let win = yield openWebIDE(); let docProject = getProjectDocument(win); let docRuntime = getRuntimeDocument(win); win.AppManager.update("runtime-list"); yield connectToLocal(win, docRuntime); // Select main process
--- a/devtools/client/webide/test/test_runtime.html +++ b/devtools/client/webide/test/test_runtime.html @@ -13,17 +13,16 @@ </head> <body> <script type="application/javascript;version=1.8"> window.onload = function() { SimpleTest.waitForExplicitFinish(); - const { DebuggerServer } = require("devtools/server/main"); let win; SimpleTest.registerCleanupFunction(() => { Task.spawn(function*() { if (win) { yield closeWebIDE(win); } DebuggerServer.destroy(); @@ -93,27 +92,24 @@ yield winProject.projectList.importPackagedApp(packagedAppLocation); yield waitForUpdate(win, "project-validated"); let panelNode = docRuntime.querySelector("#runtime-panel"); let items = panelNode.querySelectorAll(".runtime-panel-item-usb"); is(items.length, 3, "Found 3 runtime buttons"); - let deferred = promise.defer(); - win.AppManager.connection.once( - win.Connection.Events.CONNECTED, - () => deferred.resolve()); - + let connectionsChanged = waitForConnectionChange("opened", 2); items[0].click(); ok(win.document.querySelector("window").className, "busy", "UI is busy"); yield win.UI._busyPromise; - is(Object.keys(DebuggerServer._connections).length, 1, "Connected"); + yield connectionsChanged; + is(Object.keys(DebuggerServer._connections).length, 2, "Connected"); yield waitForUpdate(win, "runtime-global-actors"); ok(isPlayActive(), "play button is enabled 1"); ok(!isStopActive(), "stop button is disabled 1"); let oldProject = win.AppManager.selectedProject; win.AppManager.selectedProject = null; @@ -124,29 +120,33 @@ win.AppManager._selectedProject = oldProject; win.UI.updateCommands(); yield nextTick(); ok(isPlayActive(), "play button is enabled 3"); ok(!isStopActive(), "stop button is disabled 3"); + connectionsChanged = waitForConnectionChange("closed", 2); yield win.Cmds.disconnectRuntime(); + yield connectionsChanged; is(Object.keys(DebuggerServer._connections).length, 0, "Disconnected"); ok(win.AppManager.selectedProject, "A project is still selected"); ok(!isPlayActive(), "play button is disabled 4"); ok(!isStopActive(), "stop button is disabled 4"); + connectionsChanged = waitForConnectionChange("opened", 2); docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click(); yield waitForUpdate(win, "runtime-targets"); - is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected"); + yield connectionsChanged; + is(Object.keys(DebuggerServer._connections).length, 2, "Locally connected"); ok(win.AppManager.isMainProcessDebuggable(), "Main process available"); // Select main process SimpleTest.executeSoon(() => { docProject.querySelectorAll("#project-panel-runtimeapps .panel-item")[0].click(); });
--- a/devtools/client/webide/test/test_telemetry.html +++ b/devtools/client/webide/test/test_telemetry.html @@ -14,17 +14,16 @@ <body> <script type="application/javascript;version=1.8"> const Telemetry = require("devtools/client/shared/telemetry"); const { _DeprecatedUSBRuntime, _WiFiRuntime, _SimulatorRuntime, _gRemoteRuntime, _gLocalRuntime, RuntimeTypes } = require("devtools/client/webide/modules/runtimes"); - const { DebuggerServer } = require("devtools/server/main"); // Because we need to gather stats for the period of time that a tool has // been opened we make use of setTimeout() to create tool active times. const TOOL_DELAY = 200; function patchTelemetry() { Telemetry.prototype.telemetryInfo = {}; Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/devtools/client/webide/test/test_toolbox.html +++ b/devtools/client/webide/test/test_toolbox.html @@ -10,18 +10,16 @@ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script> <script type="application/javascript;version=1.8" src="head.js"></script> <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> </head> <body> <script type="application/javascript;version=1.8"> - const { DebuggerServer } = require("devtools/server/main"); - window.onload = function() { SimpleTest.waitForExplicitFinish(); let win; SimpleTest.registerCleanupFunction(() => { Task.spawn(function*() { if (win) {
--- a/devtools/server/actors/call-watcher.js +++ b/devtools/server/actors/call-watcher.js @@ -228,19 +228,23 @@ var FunctionCallActor = protocol.ActorCl */ var CallWatcherActor = exports.CallWatcherActor = protocol.ActorClassWithSpec(callWatcherSpec, { initialize: function (conn, tabActor) { protocol.Actor.prototype.initialize.call(this, conn); this.tabActor = tabActor; this._onGlobalCreated = this._onGlobalCreated.bind(this); this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this); this._onContentFunctionCall = this._onContentFunctionCall.bind(this); + on(this.tabActor, "window-ready", this._onGlobalCreated); + on(this.tabActor, "window-destroyed", this._onGlobalDestroyed); }, destroy: function (conn) { protocol.Actor.prototype.destroy.call(this, conn); + off(this.tabActor, "window-ready", this._onGlobalCreated); + off(this.tabActor, "window-destroyed", this._onGlobalDestroyed); this.finalize(); }, /** * Lightweight listener invoked whenever an instrumented function is called * while recording. We're doing this to avoid the event emitter overhead, * since this is expected to be a very hot function. */ @@ -259,19 +263,16 @@ var CallWatcherActor = exports.CallWatch this._timestampEpoch = 0; this._functionCalls = []; this._tracedGlobals = tracedGlobals || []; this._tracedFunctions = tracedFunctions || []; this._holdWeak = !!holdWeak; this._storeCalls = !!storeCalls; - on(this.tabActor, "window-ready", this._onGlobalCreated); - on(this.tabActor, "window-destroyed", this._onGlobalDestroyed); - if (startRecording) { this.resumeRecording(); } if (performReload) { this.tabActor.window.location.reload(); } }, @@ -282,19 +283,16 @@ var CallWatcherActor = exports.CallWatch */ finalize: function () { if (!this._initialized) { return; } this._initialized = false; this._finalized = true; - off(this.tabActor, "window-ready", this._onGlobalCreated); - off(this.tabActor, "window-destroyed", this._onGlobalDestroyed); - this._tracedGlobals = null; this._tracedFunctions = null; }, /** * Returns whether the instrumented function calls are currently recorded. */ isRecording: function () { @@ -330,16 +328,20 @@ var CallWatcherActor = exports.CallWatch eraseRecording: function () { this._functionCalls = []; }, /** * Invoked whenever the current tab actor's document global is created. */ _onGlobalCreated: function ({window, id, isTopLevel}) { + if (!this._initialized) { + return; + } + // TODO: bug 981748, support more than just the top-level documents. if (!isTopLevel) { return; } let self = this; this._tracedWindowId = id;
--- a/devtools/server/actors/childtab.js +++ b/devtools/server/actors/childtab.js @@ -1,14 +1,15 @@ /* 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/. */ "use strict"; +var { Cr } = require("chrome"); var { TabActor } = require("devtools/server/actors/webbrowser"); /** * Tab actor for documents living in a child process. * * Depends on TabActor, defined in webbrowser.js. */ @@ -49,20 +50,32 @@ Object.defineProperty(ContentActor.proto return this.window.document.title; }, enumerable: true, configurable: true }); ContentActor.prototype.exit = function () { if (this._sendForm) { - this._chromeGlobal.removeMessageListener("debug:form", this._sendForm); + try { + this._chromeGlobal.removeMessageListener("debug:form", this._sendForm); + } catch (e) { + if (e.result != Cr.NS_ERROR_NULL_POINTER) { + throw e; + } + // In some cases, especially when using messageManagers in non-e10s mode, we reach + // this point with a dead messageManager which only throws errors but does not + // seem to indicate in any other way that it is dead. + } this._sendForm = null; } - return TabActor.prototype.exit.call(this); + + TabActor.prototype.exit.call(this); + + this._chromeGlobal = null; }; /** * On navigation events, our URL and/or title may change, so we update our * counterpart in the parent process that participates in the tab list. */ ContentActor.prototype._sendForm = function () { this._chromeGlobal.sendAsyncMessage("debug:form", this.form());
--- a/devtools/server/actors/director-manager.js +++ b/devtools/server/actors/director-manager.js @@ -1,10 +1,8 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */ -/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const events = require("sdk/event/core"); const protocol = require("devtools/shared/protocol"); @@ -120,18 +118,23 @@ var MessagePortActor = exports.MessagePo * raise an exception if the port is null */ close: function () { if (!this.port) { console.error(ERR_MESSAGEPORT_FINALIZED); return; } - this.port.onmessage = null; - this.port.close(); + try { + this.port.onmessage = null; + this.port.close(); + } catch (e) { + // The port might be a dead object + console.error(e); + } }, finalize: function () { this.close(); this.port = null; }, });
--- a/devtools/server/actors/director-registry.js +++ b/devtools/server/actors/director-registry.js @@ -146,79 +146,80 @@ exports.setupParentProcess = function se return DirectorRegistry.list(); default: console.error(ERR_DIRECTOR_PARENT_UNKNOWN_METHOD, msg.json.method); throw new Error(ERR_DIRECTOR_PARENT_UNKNOWN_METHOD); } } }; -// skip child setup if this actor module is not running in a child process -if (DebuggerServer.isInChildProcess) { - setupChildProcess(); -} - -function setupChildProcess() { - const { sendSyncMessage } = DebuggerServer.parentMessageManager; - - DebuggerServer.setupInParent({ - module: "devtools/server/actors/director-registry", - setupParent: "setupParentProcess" - }); - - DirectorRegistry.install = notImplemented.bind(null, "install"); - DirectorRegistry.uninstall = notImplemented.bind(null, "uninstall"); - DirectorRegistry.clear = notImplemented.bind(null, "clear"); - - DirectorRegistry.get = callParentProcess.bind(null, "get"); - DirectorRegistry.list = callParentProcess.bind(null, "list"); - - /* child process helpers */ - - function notImplemented(method) { - console.error(ERR_DIRECTOR_CHILD_NOTIMPLEMENTED_METHOD, method); - throw Error(ERR_DIRECTOR_CHILD_NOTIMPLEMENTED_METHOD); - } - - function callParentProcess(method, ...args) { - var reply = sendSyncMessage("debug:director-registry-request", { - method: method, - args: args - }); - - if (reply.length === 0) { - console.error(ERR_DIRECTOR_CHILD_NO_REPLY); - throw Error(ERR_DIRECTOR_CHILD_NO_REPLY); - } else if (reply.length > 1) { - console.error(ERR_DIRECTOR_CHILD_MULTIPLE_REPLIES); - throw Error(ERR_DIRECTOR_CHILD_MULTIPLE_REPLIES); - } - - return reply[0]; - } -} - /** * The DirectorRegistry Actor is a global actor which manages install/uninstall of * director scripts definitions. */ const DirectorRegistryActor = exports.DirectorRegistryActor = protocol.ActorClassWithSpec(directorRegistrySpec, { /* init & destroy methods */ initialize: function (conn, parentActor) { protocol.Actor.prototype.initialize.call(this, conn); + this.maybeSetupChildProcess(conn); }, destroy: function (conn) { protocol.Actor.prototype.destroy.call(this, conn); this.finalize(); }, finalize: function () { // nothing to cleanup }, + maybeSetupChildProcess(conn) { + // skip child setup if this actor module is not running in a child process + if (!DebuggerServer.isInChildProcess) { + return; + } + + const { sendSyncMessage } = conn.parentMessageManager; + + conn.setupInParent({ + module: "devtools/server/actors/director-registry", + setupParent: "setupParentProcess" + }); + + DirectorRegistry.install = notImplemented.bind(null, "install"); + DirectorRegistry.uninstall = notImplemented.bind(null, "uninstall"); + DirectorRegistry.clear = notImplemented.bind(null, "clear"); + + DirectorRegistry.get = callParentProcess.bind(null, "get"); + DirectorRegistry.list = callParentProcess.bind(null, "list"); + + /* child process helpers */ + + function notImplemented(method) { + console.error(ERR_DIRECTOR_CHILD_NOTIMPLEMENTED_METHOD, method); + throw Error(ERR_DIRECTOR_CHILD_NOTIMPLEMENTED_METHOD); + } + + function callParentProcess(method, ...args) { + var reply = sendSyncMessage("debug:director-registry-request", { + method: method, + args: args + }); + + if (reply.length === 0) { + console.error(ERR_DIRECTOR_CHILD_NO_REPLY); + throw Error(ERR_DIRECTOR_CHILD_NO_REPLY); + } else if (reply.length > 1) { + console.error(ERR_DIRECTOR_CHILD_MULTIPLE_REPLIES); + throw Error(ERR_DIRECTOR_CHILD_MULTIPLE_REPLIES); + } + + return reply[0]; + } + }, + /** * Install a new director-script definition. * * @param String id * The director-script definition identifier. * @param String scriptCode * The director-script javascript source. * @param Object scriptOptions
--- a/devtools/server/actors/root.js +++ b/devtools/server/actors/root.js @@ -191,16 +191,24 @@ RootActor.prototype = { from: this.actorID, applicationType: this.applicationType, /* This is not in the spec, but it's used by tests. */ testConnectionPrefix: this.conn.prefix, traits: this.traits }; }, + forwardingCancelled: function (prefix) { + return { + from: this.actorID, + type: "forwardingCancelled", + prefix, + }; + }, + /** * Disconnects the actor from the browser window. */ disconnect: function () { /* Tell the live lists we aren't watching any more. */ if (this._parameters.tabList) { this._parameters.tabList.onListChanged = null; }
--- a/devtools/server/actors/utils/actor-registry-utils.js +++ b/devtools/server/actors/utils/actor-registry-utils.js @@ -15,19 +15,31 @@ const promise = require("promise"); * Support for actor registration. Main used by ActorRegistryActor * for dynamic registration of new actors. * * @param sourceText {String} Source of the actor implementation * @param fileName {String} URL of the actor module (for proper stack traces) * @param options {Object} Configuration object */ exports.registerActor = function (sourceText, fileName, options) { + // Register in the current process + exports.registerActorInCurrentProcess(sourceText, fileName, options); + // Register in any child processes + return DebuggerServer.setupInChild({ + module: "devtools/server/actors/utils/actor-registry-utils", + setupChild: "registerActorInCurrentProcess", + args: [sourceText, fileName, options], + waitForEval: true + }); +}; + +exports.registerActorInCurrentProcess = function (sourceText, fileName, options) { const principal = CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(); const sandbox = Cu.Sandbox(principal); - const exports = sandbox.exports = {}; + sandbox.exports = {}; sandbox.require = require; Cu.evalInSandbox(sourceText, sandbox, "1.8", fileName, 1); let { prefix, constructor, type } = options; if (type.global && !DebuggerServer.globalActorFactories.hasOwnProperty(prefix)) { DebuggerServer.addGlobalActor({ @@ -37,41 +49,30 @@ exports.registerActor = function (source } if (type.tab && !DebuggerServer.tabActorFactories.hasOwnProperty(prefix)) { DebuggerServer.addTabActor({ constructorName: constructor, constructorFun: sandbox[constructor] }, prefix); } - - // Also register in all child processes in case the current scope - // is chrome parent process. - if (!DebuggerServer.isInChildProcess) { - return DebuggerServer.setupInChild({ - module: "devtools/server/actors/utils/actor-registry-utils", - setupChild: "registerActor", - args: [sourceText, fileName, options], - waitForEval: true - }); - } - return promise.resolve(); }; exports.unregisterActor = function (options) { + // Unregister in the current process + exports.unregisterActorInCurrentProcess(options); + // Unregister in any child processes + DebuggerServer.setupInChild({ + module: "devtools/server/actors/utils/actor-registry-utils", + setupChild: "unregisterActorInCurrentProcess", + args: [options] + }); +}; + +exports.unregisterActorInCurrentProcess = function (options) { if (options.tab) { DebuggerServer.removeTabActor(options); } if (options.global) { DebuggerServer.removeGlobalActor(options); } - - // Also unregister it from all child processes in case the current - // scope is chrome parent process. - if (!DebuggerServer.isInChildProcess) { - DebuggerServer.setupInChild({ - module: "devtools/server/actors/utils/actor-registry-utils", - setupChild: "unregisterActor", - args: [options] - }); - } };
--- a/devtools/server/actors/webaudio.js +++ b/devtools/server/actors/webaudio.js @@ -699,16 +699,24 @@ var WebAudioActor = exports.WebAudioActo } }, /** * Ensures that the new global has recording on * so we can proxy the function calls. */ _onGlobalCreated: function () { + // Used to track when something is happening with the web audio API + // the first time, to ultimately fire `start-context` event + this._firstNodeCreated = false; + + // Clear out stored nativeIDs on reload as we do not want to track + // AudioNodes that are no longer on this document. + this._nativeToActorID.clear(); + this._callWatcher.resumeRecording(); }, /** * Fired when an automation event is added to an AudioNode. */ _onAutomationEvent: function ({node, paramName, eventName, args}) { emit(this, "automation-event", {
--- a/devtools/server/actors/webbrowser.js +++ b/devtools/server/actors/webbrowser.js @@ -310,20 +310,16 @@ BrowserTabList.prototype._getChildren = return gBrowser.browsers.filter(browser => { // Filter tabs that are closing. listTabs calls made right after TabClose // events still list tabs in process of being closed. let tab = gBrowser.getTabForBrowser(browser); return !tab.closing; }); }; -BrowserTabList.prototype._isRemoteBrowser = function (browser) { - return browser.getAttribute("remote") == "true"; -}; - BrowserTabList.prototype.getList = function () { let topXULWindow = Services.wm.getMostRecentWindow( DebuggerServer.chromeWindowType); let selectedBrowser = null; if (topXULWindow) { selectedBrowser = this._getSelectedBrowser(topXULWindow); } @@ -362,27 +358,22 @@ BrowserTabList.prototype.getList = funct }; BrowserTabList.prototype._getActorForBrowser = function (browser) { // Do we have an existing actor for this browser? If not, create one. let actor = this._actorByBrowser.get(browser); if (actor) { this._foundCount++; return actor.update(); - } else if (this._isRemoteBrowser(browser)) { - actor = new RemoteBrowserTabActor(this._connection, browser); - this._actorByBrowser.set(browser, actor); - this._checkListening(); - return actor.connect(); } actor = new BrowserTabActor(this._connection, browser); this._actorByBrowser.set(browser, actor); this._checkListening(); - return promise.resolve(actor); + return actor.connect(); }; BrowserTabList.prototype.getTab = function ({ outerWindowID, tabId }) { if (typeof outerWindowID == "number") { // First look for in-process frames with this ID let window = Services.wm.getOuterWindowWithId(outerWindowID); // Safety check to prevent debugging top level window via getTab if (window instanceof Ci.nsIDOMChromeWindow) { @@ -613,18 +604,17 @@ DevToolsUtils.makeInfallible(function (e case "TabClose": { let actor = this._actorByBrowser.get(browser); if (actor) { this._handleActorClose(actor, browser); } break; } case "TabRemotenessChange": { - // We have to remove the cached actor as we have to create a new instance - // based on BrowserTabActor or RemoteBrowserTabActor. + // We have to remove the cached actor as we have to create a new instance. let actor = this._actorByBrowser.get(browser); if (actor) { this._actorByBrowser.delete(browser); // Don't create a new actor; iterate will take care of that. Just notify. this._notifyListChanged(); this._checkListening(); } break; @@ -841,19 +831,18 @@ exports.BrowserTabList = BrowserTabList; * Note that *all* these events are dispatched in the following order * when we switch the context of the TabActor to a given iframe: * - will-navigate * - window-destroyed * - changed-toplevel-document * - window-ready * - navigate * - * This class is subclassed by BrowserTabActor and - * ContentActor. Subclasses are expected to implement a getter - * for the docShell property. + * This class is subclassed by ContentActor and others. + * Subclasses are expected to implement a getter for the docShell property. * * @param connection DebuggerServerConnection * The conection to the client. */ function TabActor(connection) { this.conn = connection; this._tabActorPool = null; // A map of actor names to actor instances provided by extensions. @@ -2138,137 +2127,54 @@ TabActor.prototype.requestTypes = { "listFrames": TabActor.prototype.onListFrames, "listWorkers": TabActor.prototype.onListWorkers, "resolveLocation": TabActor.prototype.onResolveLocation }; exports.TabActor = TabActor; /** - * Creates a tab actor for handling requests to a single in-process - * <xul:browser> tab, or <html:iframe>. - * Most of the implementation comes from TabActor. + * Creates a tab actor for handling requests to a single browser frame. + * Both <xul:browser> and <iframe mozbrowser> are supported. + * This actor is a shim that connects to a ContentActor in a remote browser process. + * All RDP packets get forwarded using the message manager. * - * @param connection DebuggerServerConnection - * The connection to the client. - * @param browser browser - * The frame instance that contains this tab. + * @param connection The main RDP connection. + * @param browser <xul:browser> or <iframe mozbrowser> element to connect to. */ function BrowserTabActor(connection, browser) { - TabActor.call(this, connection, browser); - this._browser = browser; - if (typeof browser.getTabBrowser == "function") { - this._tabbrowser = browser.getTabBrowser(); - } - - Object.defineProperty(this, "docShell", { - value: this._browser.docShell, - configurable: true - }); -} - -BrowserTabActor.prototype = Object.create(TabActor.prototype); - -BrowserTabActor.prototype.constructor = BrowserTabActor; - -Object.defineProperty(BrowserTabActor.prototype, "title", { - get() { - // On Fennec, we can check the session store data for zombie tabs - if (this._browser.__SS_restore) { - let sessionStore = this._browser.__SS_data; - // Get the last selected entry - let entry = sessionStore.entries[sessionStore.index - 1]; - return entry.title; - } - let title = this.contentDocument.title || this._browser.contentTitle; - // If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a - // tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label - // as the title. - if (!title && this._tabbrowser) { - let tab = this._tabbrowser._getTabForContentWindow(this.window); - if (tab) { - title = tab.label; - } - } - return title; - }, - enumerable: true, - configurable: false -}); - -Object.defineProperty(BrowserTabActor.prototype, "url", { - get() { - // On Fennec, we can check the session store data for zombie tabs - if (this._browser.__SS_restore) { - let sessionStore = this._browser.__SS_data; - // Get the last selected entry - let entry = sessionStore.entries[sessionStore.index - 1]; - return entry.url; - } - if (this.webNavigation.currentURI) { - return this.webNavigation.currentURI.spec; - } - return null; - }, - enumerable: true, - configurable: true -}); - -Object.defineProperty(BrowserTabActor.prototype, "browser", { - get() { - return this._browser; - }, - enumerable: true, - configurable: false -}); - -BrowserTabActor.prototype.disconnect = function () { - TabActor.prototype.disconnect.call(this); - this._browser = null; - this._tabbrowser = null; -}; - -BrowserTabActor.prototype.exit = function () { - TabActor.prototype.exit.call(this); - this._browser = null; - this._tabbrowser = null; -}; - -exports.BrowserTabActor = BrowserTabActor; - -/** - * This actor is a shim that connects to a ContentActor in a remote - * browser process. All RDP packets get forwarded using the message - * manager. - * - * @param connection The main RDP connection. - * @param browser XUL <browser> element to connect to. - */ -function RemoteBrowserTabActor(connection, browser) { this._conn = connection; this._browser = browser; this._form = null; } -RemoteBrowserTabActor.prototype = { +BrowserTabActor.prototype = { connect() { let onDestroy = () => { this._form = null; }; - let connect = DebuggerServer.connectToChild( - this._conn, this._browser, onDestroy); + let connect = DebuggerServer.connectToChild(this._conn, this._browser, onDestroy); return connect.then(form => { this._form = form; return this; }); }, + get _tabbrowser() { + if (typeof this._browser.getTabBrowser == "function") { + return this._browser.getTabBrowser(); + } + return null; + }, + get _mm() { - return this._browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader - .messageManager; + // Get messageManager from XUL browser (which might be a specialized tunnel for RDM) + // or else fallback to asking the frameLoader itself. + return this._browser.messageManager || + this._browser.frameLoader.messageManager; }, update() { // If the child happens to be crashed/close/detach, it won't have _form set, // so only request form update if some code is still listening on the other // side. if (this._form) { let deferred = promise.defer(); @@ -2284,26 +2190,75 @@ RemoteBrowserTabActor.prototype = { this._mm.addMessageListener("debug:form", onFormUpdate); this._mm.sendAsyncMessage("debug:form"); return deferred.promise; } return this.connect(); }, + /** + * If we don't have a title from the content side because it's a zombie tab, try to find + * it on the chrome side. + */ + get title() { + // On Fennec, we can check the session store data for zombie tabs + if (this._browser.__SS_restore) { + let sessionStore = this._browser.__SS_data; + // Get the last selected entry + let entry = sessionStore.entries[sessionStore.index - 1]; + return entry.title; + } + // If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a + // tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label + // as the title. + if (this._tabbrowser) { + let tab = this._tabbrowser.getTabForBrowser(this._browser); + if (tab) { + return tab.label; + } + } + return ""; + }, + + /** + * If we don't have a url from the content side because it's a zombie tab, try to find + * it on the chrome side. + */ + get url() { + // On Fennec, we can check the session store data for zombie tabs + if (this._browser.__SS_restore) { + let sessionStore = this._browser.__SS_data; + // Get the last selected entry + let entry = sessionStore.entries[sessionStore.index - 1]; + return entry.url; + } + return null; + }, + form() { - return this._form; + let form = Object.assign({}, this._form); + // In some cases, the title and url fields might be empty. Zombie tabs (not yet + // restored) are a good example. In such cases, try to look up values for these + // fields using other data in the parent process. + if (!form.title) { + form.title = this.title; + } + if (!form.url) { + form.url = this.url; + } + return form; }, exit() { this._browser = null; }, }; -exports.RemoteBrowserTabActor = RemoteBrowserTabActor; +exports.BrowserTabActor = BrowserTabActor; function BrowserAddonList(connection) { this._connection = connection; this._actorByAddonId = new Map(); this._onListChanged = null; } BrowserAddonList.prototype.getList = function () {
--- a/devtools/server/actors/webconsole.js +++ b/devtools/server/actors/webconsole.js @@ -603,17 +603,19 @@ WebConsoleActor.prototype = case "NetworkActivity": if (!this.networkMonitor) { // Create a StackTraceCollector that's going to be shared both by the // NetworkMonitorChild (getting messages about requests from parent) and // by the NetworkMonitor that directly watches service workers requests. this.stackTraceCollector = new StackTraceCollector({ window, appId }); this.stackTraceCollector.init(); - if (appId || messageManager) { + let processBoundary = Services.appinfo.processType != + Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; + if ((appId || messageManager) && processBoundary) { // Start a network monitor in the parent process to listen to // most requests than happen in parent this.networkMonitor = new NetworkMonitorChild(appId, messageManager, this.parentActor.actorID, this); this.networkMonitor.init(); // Spawn also one in the child to listen to service workers this.networkMonitorChild = new NetworkMonitor({ window }, this);
--- a/devtools/server/child.js +++ b/devtools/server/child.js @@ -1,32 +1,33 @@ /* 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/. */ "use strict"; -/* global addMessageListener, removeMessageListener, sendAsyncMessage */ +/* global addEventListener, addMessageListener, removeMessageListener, sendAsyncMessage */ try { var chromeGlobal = this; // Encapsulate in its own scope to allows loading this frame script more than once. (function () { - let Cu = Components.utils; - let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); + const Cu = Components.utils; + const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); + const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const { dumpn } = DevToolsUtils; const { DebuggerServer, ActorPool } = require("devtools/server/main"); - // Note that this frame script may be evaluated in non-e10s build. In such case, - // DebuggerServer is already going to be initialized. if (!DebuggerServer.initialized) { DebuggerServer.init(); - DebuggerServer.isInChildProcess = true; + // For non-e10s mode, there is only one server instance, so be sure the browser + // actors get loaded. + DebuggerServer.addBrowserActors(); } // In case of apps being loaded in parent process, DebuggerServer is already // initialized, but child specific actors are not registered. Otherwise, for apps in // child process, we need to load actors the first time we load child.js. DebuggerServer.addChildActors(); let connections = new Map(); @@ -93,16 +94,26 @@ try { let conn = connections.get(prefix); if (conn) { conn.close(); connections.delete(prefix); } }); addMessageListener("debug:disconnect", onDisconnect); + // In non-e10s mode, the "debug:disconnect" message isn't always received before the + // messageManager connection goes away. Watching for "unload" here ensures we close + // any connections when the frame is unloaded. + addEventListener("unload", () => { + for (let conn of connections.values()) { + conn.close(); + } + connections.clear(); + }); + let onInspect = DevToolsUtils.makeInfallible(function (msg) { // Store the node to be inspected in a global variable (gInspectingNode). Later // we'll fetch this variable again using the findInspectingNode request over the // remote debugging protocol. let inspector = require("devtools/server/actors/inspector"); inspector.setInspectingNode(msg.objects.node); }); addMessageListener("debug:inspect", onInspect);
--- a/devtools/server/content-server.jsm +++ b/devtools/server/content-server.jsm @@ -18,17 +18,16 @@ function init(msg) { // the debugger with all devtools modules, nor break the debugger itself with using it // in the same process. let devtools = new DevToolsLoader(); devtools.invisibleToDebugger = true; let { DebuggerServer, ActorPool } = devtools.require("devtools/server/main"); if (!DebuggerServer.initialized) { DebuggerServer.init(); - DebuggerServer.isInChildProcess = true; } // In case of apps being loaded in parent process, DebuggerServer is already // initialized, but child specific actors are not registered. // Otherwise, for child process, we need to load actors the first // time we load child.js DebuggerServer.addChildActors();
--- a/devtools/server/main.js +++ b/devtools/server/main.js @@ -891,39 +891,37 @@ var DebuggerServer = { }); } }; dbg.addListener(listener); }); }, /** - * Check if the caller is running in a content child process. - * (Eventually set by child.js) - * - * @return boolean - * true if the caller is running in a content + * Check if the server is running in the child process. */ - isInChildProcess: false, + get isInChildProcess() { + return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; + }, /** * In a chrome parent process, ask all content child processes * to execute a given module setup helper. * * @param module * The module to be required * @param setupChild * The name of the setup helper exported by the above module * (setup helper signature: function ({mm}) { ... }) * @param waitForEval (optional) * If true, the returned promise only resolves once code in child * is evaluated */ setupInChild({ module, setupChild, args, waitForEval }) { - if (this.isInChildProcess || this._childMessageManagers.size == 0) { + if (this._childMessageManagers.size == 0) { return Promise.resolve(); } let deferred = Promise.defer(); // If waitForEval is set, pass a unique id and expect child.js to send // a message back once the code in child is evaluated. if (typeof (waitForEval) != "boolean") { waitForEval = false; @@ -977,17 +975,19 @@ var DebuggerServer = { * related TabActor) * @return object * A promise object that is resolved once the connection is * established. */ connectToChild(connection, frame, onDestroy) { let deferred = SyncPromise.defer(); - let mm = frame.frameLoader.messageManager; + // Get messageManager from XUL browser (which might be a specialized tunnel for RDM) + // or else fallback to asking the frameLoader itself. + let mm = frame.messageManager || frame.frameLoader.messageManager; mm.loadFrameScript("resource://devtools/server/child.js", false); this._childMessageManagers.add(mm); let actor, childTransport; let prefix = connection.allocID("child"); let netMonitor = null; // provides hook to actor modules that need to exchange messages @@ -1293,17 +1293,32 @@ var DebuggerServer = { if ((handler.name && handler.name == actor.name) || (handler.id && handler.id == actor.id)) { delete DebuggerServer.globalActorFactories[name]; for (let connID of Object.getOwnPropertyNames(this._connections)) { this._connections[connID].rootActor.removeActorByName(name); } } } - } + }, + + /** + * âš TESTING ONLY! âš Searches all active connections for an actor matching an ID. + * This is helpful for some tests which depend on reaching into the server to check some + * properties of an actor. + */ + _searchAllConnectionsForActor(actorID) { + for (let connID of Object.getOwnPropertyNames(this._connections)) { + let actor = this._connections[connID].getActor(actorID); + if (actor) { + return actor; + } + } + return null; + }, }; // Expose these to save callers the trouble of importing DebuggerSocket DevToolsUtils.defineLazyGetter(DebuggerServer, "Authenticators", () => { return Authentication.Authenticators; }); DevToolsUtils.defineLazyGetter(DebuggerServer, "AuthenticationResult", () => { return Authentication.AuthenticationResult; @@ -1385,17 +1400,19 @@ DebuggerServerConnection.prototype = { /** * Message manager used to communicate with the parent process, * set by child.js. Is only defined for connections instantiated * within a child process. */ parentMessageManager: null, close() { - this._transport.close(); + if (this._transport) { + this._transport.close(); + } }, send(packet) { this.transport.send(packet); }, /** * Used when sending a bulk reply from an actor. @@ -1589,16 +1606,23 @@ DebuggerServerConnection.prototype = { }, /* * Stop forwarding messages to actors whose names begin with * |prefix+'/'|. Such messages will now elicit 'noSuchActor' errors. */ cancelForwarding(prefix) { this._forwardingPrefixes.delete(prefix); + + // Notify the client that forwarding in now cancelled for this prefix. + // There could be requests in progress that the client should abort rather leaving + // handing indefinitely. + if (this.rootActor) { + this.send(this.rootActor.forwardingCancelled(prefix)); + } }, sendActorEvent(actorID, eventName, event = {}) { event.from = actorID; event.type = eventName; this.send(event); },
--- a/devtools/server/tests/browser/browser.ini +++ b/devtools/server/tests/browser/browser.ini @@ -62,19 +62,19 @@ skip-if = e10s # Bug 1183605 - devtools/ [browser_markers-styles.js] [browser_markers-timestamp.js] [browser_navigateEvents.js] skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S [browser_perf-allocation-data.js] skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S [browser_perf-profiler-01.js] [browser_perf-profiler-02.js] -skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +skip-if = true # Needs to be updated for async actor destruction [browser_perf-profiler-03.js] -skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +skip-if = true # Needs to be updated for async actor destruction [browser_perf-realtime-markers.js] [browser_perf-recording-actor-01.js] skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S [browser_perf-recording-actor-02.js] skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S [browser_perf-samples-01.js] [browser_perf-samples-02.js] #[browser_perf-front-profiler-01.js] bug 1077464
--- a/devtools/server/tests/browser/browser_navigateEvents.js +++ b/devtools/server/tests/browser/browser_navigateEvents.js @@ -27,26 +27,26 @@ function assertEvent(event, data) { case x++: is(event, "unload-dialog", "We get the dialog on first page unload"); break; case x++: is(event, "will-navigate", "The very first event is will-navigate on server side"); is(data.newURI, URL2, "newURI property is correct"); break; case x++: - is(event, "tabNavigated", "Right after will-navigate, the client receive tabNavigated"); + is(event, "request", "RDP is async with messageManager, the request happens after will-navigate"); + is(data, URL2); + break; + case x++: + is(event, "tabNavigated", "After the request, the client receive tabNavigated"); is(data.state, "start", "state is start"); is(data.url, URL2, "url property is correct"); is(data.nativeConsoleAPI, true, "nativeConsoleAPI is correct"); break; case x++: - is(event, "request", "Given that locally, the Debugger protocol is sync, the request happens after tabNavigated"); - is(data, URL2); - break; - case x++: is(event, "DOMContentLoaded"); is(content.document.readyState, "interactive"); break; case x++: is(event, "load"); is(content.document.readyState, "complete"); break; case x++: @@ -101,18 +101,17 @@ function getServerTabActor(callback) { // Connect to this tab let transport = DebuggerServer.connectPipe(); client = new DebuggerClient(transport); connectDebuggerClient(client).then(form => { let actorID = form.actor; client.attachTab(actorID, function (aResponse, aTabClient) { // !Hack! Retrieve a server side object, the BrowserTabActor instance - let conn = transport._serverConnection; - let tabActor = conn.getActor(actorID); + let tabActor = DebuggerServer._searchAllConnectionsForActor(actorID); callback(tabActor); }); }); client.addListener("tabNavigated", function (aEvent, aPacket) { assertEvent("tabNavigated", aPacket); }); }
--- a/devtools/server/tests/browser/head.js +++ b/devtools/server/tests/browser/head.js @@ -56,17 +56,19 @@ function* initAnimationsFrontForUrl(url) return {inspector, walker, animations, client}; } function initDebuggerServer() { try { // Sometimes debugger server does not get destroyed correctly by previous // tests. DebuggerServer.destroy(); - } catch (ex) { } + } catch (e) { + info(`DebuggerServer destroy error: ${e}\n${e.stack}`); + } DebuggerServer.init(); DebuggerServer.addBrowserActors(); } /** * Connect a debugger client. * @param {DebuggerClient} * @return {Promise} Resolves to the selected tabActor form when the client is
--- a/devtools/server/tests/mochitest/inspector-helpers.js +++ b/devtools/server/tests/mochitest/inspector-helpers.js @@ -116,18 +116,17 @@ function serverOwnershipSubtree(walker, } return { name: actor.actorID, children: sortOwnershipChildren(children) }; } function serverOwnershipTree(walker) { - let serverConnection = walker.conn._transport._serverConnection; - let serverWalker = serverConnection.getActor(walker.actorID); + let serverWalker = DebuggerServer._searchAllConnectionsForActor(walker.actorID); return { root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc), orphaned: [...serverWalker._orphaned].map(o => serverOwnershipSubtree(serverWalker, o.rawNode)), retained: [...serverWalker._retainedOrphans].map(o => serverOwnershipSubtree(serverWalker, o.rawNode)) }; }
--- a/devtools/server/tests/mochitest/test_animation_actor-lifetime.html +++ b/devtools/server/tests/mochitest/test_animation_actor-lifetime.html @@ -42,18 +42,17 @@ window.onload = function() { addAsyncTest(function* testActorLifetime() { info ("Testing animated node actor"); let animatedNodeActor = yield gWalker.querySelector(gWalker.rootNode, ".animated"); yield animationsFront.getAnimationPlayersForNode(animatedNodeActor); - let serverConnection = animationsFront.conn._transport._serverConnection; - let animationsActor = serverConnection.getActor(animationsFront.actorID); + let animationsActor = DebuggerServer._searchAllConnectionsForActor(animationsFront.actorID); is(animationsActor.actors.length, 1, "AnimationActor have 1 AnimationPlayerActors"); info ("Testing AnimationPlayerActors release"); let stillNodeActor = yield gWalker.querySelector(gWalker.rootNode, ".still"); yield animationsFront.getAnimationPlayersForNode(stillNodeActor);
--- a/devtools/server/tests/mochitest/test_inspector-anonymous.html +++ b/devtools/server/tests/mochitest/test_inspector-anonymous.html @@ -67,18 +67,17 @@ window.onload = function() { is (children.nodes.length, 2, "No native anon content for form control"); runNextTest(); }); addAsyncTest(function* testNativeAnonymousStartingNode() { info ("Tests attaching an element that a walker can't see."); - let serverConnection = gWalker.conn._transport._serverConnection; - let serverWalker = serverConnection.getActor(gWalker.actorID); + let serverWalker = DebuggerServer._searchAllConnectionsForActor(gWalker.actorID); let docwalker = new _documentWalker( gInspectee.querySelector("select"), gInspectee.defaultView, nodeFilterConstants.SHOW_ALL, () => { return nodeFilterConstants.FILTER_ACCEPT } );
--- a/devtools/server/tests/mochitest/test_inspector-dead-nodes.html +++ b/devtools/server/tests/mochitest/test_inspector-dead-nodes.html @@ -37,314 +37,336 @@ addAsyncTest(function() { runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.parents(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.parents(nodeFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.parents() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.children(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "body"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.children(nodeFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.children() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.siblings(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.siblings(nodeFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.siblings() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.nextSibling(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.nextSibling(nodeFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.nextSibling() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.previousSibling(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.previousSibling(nodeFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.previousSibling() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.addPseudoClassLock(nodeFront) before the load completes " + "shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.addPseudoClassLock(nodeFront, ":hover"); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.addPseudoClassLock() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.removePseudoClassLock(nodeFront) before the load completes " + "shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.removePseudoClassLock(nodeFront, ":hover"); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.removePseudoClassLock() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.clearPseudoClassLocks(nodeFront) before the load completes " + "shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.clearPseudoClassLocks(nodeFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.clearPseudoClassLocks() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.innerHTML(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.innerHTML(nodeFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.innerHTML() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.setInnerHTML(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.setInnerHTML(nodeFront, "<span>innerHTML changed</span>"); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.setInnerHTML() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.outerHTML(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.outerHTML(nodeFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.outerHTML() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.setOuterHTML(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.setOuterHTML(nodeFront, "<h1><span>innerHTML changed</span></h1>"); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.setOuterHTML() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.insertAdjacentHTML(nodeFront) before the load completes shouldn't " + "fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.insertAdjacentHTML(nodeFront, "afterEnd", "<span>new adjacent HTML</span>"); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.insertAdjacentHTML() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.removeNode(nodeFront) before the load completes should throw"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); let hasThrown = false; try { yield gWalker.removeNode(nodeFront); } catch (e) { hasThrown = true; } - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(hasThrown, "The call to walker.removeNode() threw"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.removeNodes([nodeFront]) before the load completes should throw"); let nodeFront1 = yield gWalker.querySelector(gWalker.rootNode, "h1"); let nodeFront2 = yield gWalker.querySelector(gWalker.rootNode, "#longstring"); let nodeFront3 = yield gWalker.querySelector(gWalker.rootNode, "#shortstring"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); let hasThrown = false; try { yield gWalker.removeNodes([nodeFront1, nodeFront2, nodeFront3]); } catch (e) { hasThrown = true; } - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(hasThrown, "The call to walker.removeNodes() threw"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.insertBefore(nodeFront, parent, null) before the load completes " + "shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); let newParentFront = yield gWalker.querySelector(gWalker.rootNode, "#longlist"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.insertBefore(nodeFront, newParentFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.insertBefore() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.insertBefore(nodeFront, parent, sibling) before the load completes " + "shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); let newParentFront = yield gWalker.querySelector(gWalker.rootNode, "#longlist"); let siblingFront = yield gWalker.querySelector(gWalker.rootNode, "#b"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.insertBefore(nodeFront, newParentFront, siblingFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.insertBefore() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.editTagName(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.editTagName(nodeFront, "h2"); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.editTagName() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.hideNode(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.hideNode(nodeFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.hideNode() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.unhideNode(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.unhideNode(nodeFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.unhideNode() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.releaseNode(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.releaseNode(nodeFront); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.releaseNode() didn't fail"); runNextTest(); }); addAsyncTest(function() { info("Getting a nodeFront, reloading the page, and calling " + "walker.querySelector(nodeFront) before the load completes shouldn't fail"); let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "body"); + let newRoot = waitForMutation(gWalker, isNewRoot); gDoc.defaultView.location.reload(); yield gWalker.querySelector(nodeFront, "h1"); - yield waitForMutation(gWalker, isNewRoot); + yield newRoot; ok(true, "The call to walker.querySelector() didn't fail"); runNextTest(); }); addTest(function cleanup() { gWalker = gDoc = null; runNextTest();
--- a/devtools/server/tests/mochitest/test_inspector-search.html +++ b/devtools/server/tests/mochitest/test_inspector-search.html @@ -41,18 +41,17 @@ window.onload = function() { inspector = InspectorFront(client, tab); resolve(); }); }); let walkerFront = yield inspector.getWalker(); ok(walkerFront, "getWalker() should return an actor."); - let serverConnection = walkerFront.conn._transport._serverConnection; - walkerActor = serverConnection.getActor(walkerFront.actorID); + walkerActor = DebuggerServer._searchAllConnectionsForActor(walkerFront.actorID); ok(walkerActor, "Got a reference to the walker actor (" + walkerFront.actorID + ")"); walkerSearch = walkerActor.walkerSearch; runNextTest(); });
--- a/devtools/server/tests/mochitest/test_setupInParentChild.html +++ b/devtools/server/tests/mochitest/test_setupInParentChild.html @@ -35,17 +35,17 @@ window.onload = function() { } function runTests() { // Create a minimal iframe with a message manager let iframe = document.createElement("iframe"); iframe.mozbrowser = true; document.body.appendChild(iframe); - let mm = iframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; + let mm = iframe.frameLoader.messageManager; // Instantiate a minimal server if (!DebuggerServer.initialized) { DebuggerServer.init(); } if (!DebuggerServer.createRootActor) { DebuggerServer.addBrowserActors(); }
--- a/devtools/server/tests/unit/test_client_request.js +++ b/devtools/server/tests/unit/test_client_request.js @@ -151,17 +151,17 @@ function test_close_client_while_sending let pendingRequest = gClient.request({ to: gActorId, type: "hello" }); let expectReply = promise.defer(); gClient.expectReply("root", function (response) { do_check_eq(response.error, "connectionClosed"); - do_check_eq(response.message, "server side packet from 'root' can't be received as the connection just closed."); + do_check_eq(response.message, "server side packet can't be received as the connection just closed."); expectReply.resolve(); }); gClient.close(() => { activeRequest.then(() => { ok(false, "First request unexpectedly succeed while closing the connection"); }, response => { do_check_eq(response.error, "connectionClosed");
--- a/devtools/shared/client/main.js +++ b/devtools/shared/client/main.js @@ -937,16 +937,26 @@ DebuggerClient.prototype = { // If we have a registered Front for this actor, let it handle the packet // and skip all the rest of this unpleasantness. let front = this.getActor(aPacket.from); if (front) { front.onPacket(aPacket); return; } + // Check for "forwardingCancelled" here instead of using a client to handle it. + // This is necessary because we might receive this event while the client is closing, + // and the clients have already been removed by that point. + if (this.mainRoot && + aPacket.from == this.mainRoot.actor && + aPacket.type == "forwardingCancelled") { + this.purgeRequests(aPacket.prefix); + return; + } + if (this._clients.has(aPacket.from) && aPacket.type) { let client = this._clients.get(aPacket.from); let type = aPacket.type; if (client.events.indexOf(type) != -1) { client.emit(type, aPacket); // we ignore the rest, as the client is expected to handle this packet. return; } @@ -1067,45 +1077,21 @@ DebuggerClient.prototype = { /** * Called by DebuggerTransport when the underlying stream is closed. * * @param aStatus nsresult * The status code that corresponds to the reason for closing * the stream. */ - onClosed: function (aStatus) { + onClosed: function () { this._closed = true; this.emit("closed"); - // Reject all pending and active requests - let reject = function (type, request, actor) { - // Server can send packets on its own and client only pass a callback - // to expectReply, so that there is no request object. - let msg; - if (request.request) { - msg = "'" + request.request.type + "' " + type + " request packet" + - " to '" + actor + "' " + - "can't be sent as the connection just closed."; - } else { - msg = "server side packet from '" + actor + "' can't be received " + - "as the connection just closed."; - } - let packet = { error: "connectionClosed", message: msg }; - request.emit("json-reply", packet); - }; - - let pendingRequests = new Map(this._pendingRequests); - this._pendingRequests.clear(); - pendingRequests.forEach((list, actor) => { - list.forEach(request => reject("pending", request, actor)); - }); - let activeRequests = new Map(this._activeRequests); - this._activeRequests.clear(); - activeRequests.forEach(reject.bind(null, "active")); + this.purgeRequests(); // The |_pools| array on the client-side currently is used only by // protocol.js to store active fronts, mirroring the actor pools found in // the server. So, read all usages of "pool" as "protocol.js front". // // In the normal case where we shutdown cleanly, the toolbox tells each tool // to close, and they each call |destroy| on any fronts they were using. // When |destroy| or |cleanup| is called on a protocol.js front, it also @@ -1119,16 +1105,61 @@ DebuggerClient.prototype = { // from |_pools|. This saves the toolbox from hanging indefinitely, in case // it waits for some server response before shutdown that will now never // arrive. for (let pool of this._pools) { pool.cleanup(); } }, + /** + * Purge pending and active requests in this client. + * + * @param prefix string (optional) + * If a prefix is given, only requests for actor IDs that start with the prefix + * will be cleaned up. This is useful when forwarding of a portion of requests + * is cancelled on the server. + */ + purgeRequests(prefix = "") { + let reject = function (type, request) { + // Server can send packets on its own and client only pass a callback + // to expectReply, so that there is no request object. + let msg; + if (request.request) { + msg = "'" + request.request.type + "' " + type + " request packet" + + " to '" + request.actor + "' " + + "can't be sent as the connection just closed."; + } else { + msg = "server side packet can't be received as the connection just closed."; + } + let packet = { error: "connectionClosed", message: msg }; + request.emit("json-reply", packet); + }; + + let pendingRequestsToReject = []; + this._pendingRequests.forEach((requests, actor) => { + if (!actor.startsWith(prefix)) { + return; + } + this._pendingRequests.delete(actor); + pendingRequestsToReject = pendingRequestsToReject.concat(requests); + }); + pendingRequestsToReject.forEach(request => reject("pending", request)); + + let activeRequestsToReject = []; + this._activeRequests.forEach((request, actor) => { + if (!actor.startsWith(prefix)) { + return; + } + this._activeRequests.delete(actor); + activeRequestsToReject = activeRequestsToReject.concat(request); + }); + activeRequestsToReject.forEach(request => reject("active", request)); + }, + registerClient: function (client) { let actorID = client.actor; if (!actorID) { throw new Error("DebuggerServer.registerClient expects " + "a client instance with an `actor` attribute."); } if (!Array.isArray(client.events)) { throw new Error("DebuggerServer.registerClient expects " +
--- a/devtools/shared/fronts/inspector.js +++ b/devtools/shared/fronts/inspector.js @@ -419,21 +419,22 @@ const NodeFront = FrontClassWithSpec(nod }, /** * Get an nsIDOMNode for the given node front. This only works locally, * and is only intended as a stopgap during the transition to the remote * protocol. If you depend on this you're likely to break soon. */ rawNode: function (rawNode) { - if (!this.conn._transport._serverConnection) { + if (!this.isLocalToBeDeprecated()) { console.warn("Tried to use rawNode on a remote connection."); return null; } - let actor = this.conn._transport._serverConnection.getActor(this.actorID); + const { DebuggerServer } = require("devtools/server/main"); + let actor = DebuggerServer._searchAllConnectionsForActor(this.actorID); if (!actor) { // Can happen if we try to get the raw node for an already-expired // actor. return null; } return actor.rawNode; } }); @@ -890,18 +891,18 @@ const WalkerFront = FrontClassWithSpec(w // XXX hack during transition to remote inspector: get a proper NodeFront // for a given local node. Only works locally. frontForRawNode: function (rawNode) { if (!this.isLocal()) { console.warn("Tried to use frontForRawNode on a remote connection."); return null; } - let walkerActor = this.conn._transport._serverConnection - .getActor(this.actorID); + const { DebuggerServer } = require("devtools/server/main"); + let walkerActor = DebuggerServer._searchAllConnectionsForActor(this.actorID); if (!walkerActor) { throw Error("Could not find client side for actor " + this.actorID); } let nodeActor = walkerActor._ref(rawNode); // Pass the node through a read/write pair to create the client side actor. let nodeType = types.getType("domnode"); let returnNode = nodeType.read( @@ -911,17 +912,17 @@ const WalkerFront = FrontClassWithSpec(w for (let extraActor of extras) { top = nodeType.read(nodeType.write(extraActor, walkerActor), this); } if (top !== this.rootNode) { // Imported an already-orphaned node. this._orphaned.add(top); walkerActor._orphaned - .add(this.conn._transport._serverConnection.getActor(top.actorID)); + .add(DebuggerServer._searchAllConnectionsForActor(top.actorID)); } return returnNode; }, removeNode: custom(Task.async(function* (node) { let previousSibling = yield this.previousSibling(node); let nextSibling = yield this._removeNode(node); return {
--- a/devtools/shared/transport/transport.js +++ b/devtools/shared/transport/transport.js @@ -17,17 +17,17 @@ factory.call(this, require, this); } else { // Cu.import const Cu = Components.utils; const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); factory.call(this, require, this); } }).call(this, function (require, exports) { - const { Cc, Ci, Cr, CC } = require("chrome"); + const { Cc, Cr, CC } = require("chrome"); const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const { dumpn, dumpv } = DevToolsUtils; const StreamUtils = require("devtools/shared/transport/stream-utils"); const { Packet, JSONPacket, BulkPacket } = require("devtools/shared/transport/packets"); const promise = require("promise"); const defer = require("devtools/shared/defer"); const EventEmitter = require("devtools/shared/event-emitter"); @@ -709,17 +709,17 @@ * multiple servers running in the same child process. * * This transport exchanges messages named 'debug:<prefix>:packet', where * <prefix> is |prefix|, whose data is the protocol packet. */ function ChildDebuggerTransport(sender, prefix) { EventEmitter.decorate(this); - this._sender = sender.QueryInterface(Ci.nsIMessageSender); + this._sender = sender; this._messageName = "debug:" + prefix + ":packet"; } /* * To avoid confusion, we use 'message' to mean something that * nsIMessageSender conveys, and 'packet' to mean a remote debugging * protocol packet. */ @@ -728,29 +728,47 @@ hooks: null, ready: function () { this._sender.addMessageListener(this._messageName, this); }, close: function () { - this._sender.removeMessageListener(this._messageName, this); + try { + this._sender.removeMessageListener(this._messageName, this); + } catch (e) { + if (e.result != Cr.NS_ERROR_NULL_POINTER) { + throw e; + } + // In some cases, especially when using messageManagers in non-e10s mode, we reach + // this point with a dead messageManager which only throws errors but does not + // seem to indicate in any other way that it is dead. + } this.emit("onClosed"); this.hooks.onClosed(); }, receiveMessage: function ({data}) { this.emit("onPacket", data); this.hooks.onPacket(data); }, send: function (packet) { this.emit("send", packet); - this._sender.sendAsyncMessage(this._messageName, packet); + try { + this._sender.sendAsyncMessage(this._messageName, packet); + } catch (e) { + if (e.result != Cr.NS_ERROR_NULL_POINTER) { + throw e; + } + // In some cases, especially when using messageManagers in non-e10s mode, we reach + // this point with a dead messageManager which only throws errors but does not + // seem to indicate in any other way that it is dead. + } }, startBulkSend: function () { throw new Error("Can't send bulk data to child processes."); } }; exports.ChildDebuggerTransport = ChildDebuggerTransport;
--- a/devtools/shared/webconsole/network-monitor.js +++ b/devtools/shared/webconsole/network-monitor.js @@ -76,18 +76,35 @@ function matchRequest(channel, filters) break; } win = win.parent; } } if (filters.topFrame) { let topFrame = NetworkHelper.getTopFrameForRequest(channel); - if (topFrame && topFrame === filters.topFrame) { - return true; + while (topFrame) { + // In the normal case, a topFrame filter should match the request's topFrame if it + // will match at all. + if (topFrame === filters.topFrame) { + return true; + } + // As a stop gap approach for RDM, where `filters.topFrame` will be the + // <xul:browser> frame for an entire tab and the request's `topFrame` is the + // <iframe mozbrower> that triggered the request, we try to climb up parent frames + // above the request's `topFrame` to see if they might also match the filter. + // In bug 1240912, we want to rework this, since we don't really want to be passing + // a frame down to the network monitor. + if (!topFrame.ownerGlobal) { + break; + } + topFrame = topFrame.ownerGlobal + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .containerElement; } } if (filters.appId) { let appId = NetworkHelper.getAppIdForRequest(channel); if (appId && appId == filters.appId) { return true; } @@ -1578,18 +1595,19 @@ NetworkEventActorProxy.prototype = { * @constructor * @param nsIDOMElement frame * The browser frame to work with (mozbrowser). * @param string id * Instance identifier to use for messages. */ function NetworkMonitorManager(frame, id) { this.id = id; - let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader - .messageManager; + // Get messageManager from XUL browser (which might be a specialized tunnel for RDM) + // or else fallback to asking the frameLoader itself. + let mm = frame.messageManager || frame.frameLoader.messageManager; this.messageManager = mm; this.frame = frame; this.onNetMonitorMessage = this.onNetMonitorMessage.bind(this); this.onNetworkEvent = this.onNetworkEvent.bind(this); mm.addMessageListener("debug:netmonitor:" + id, this.onNetMonitorMessage); } exports.NetworkMonitorManager = NetworkMonitorManager;
--- a/dom/animation/KeyframeEffect.cpp +++ b/dom/animation/KeyframeEffect.cpp @@ -604,17 +604,17 @@ KeyframeEffectReadOnly::UpdateProperties for (AnimationProperty& property : mProperties) { property.mWinsInCascade = winningInCascadeProperties.HasProperty(property.mProperty); property.mIsRunningOnCompositor = runningOnCompositorProperties.HasProperty(property.mProperty); } - CalculateCumulativeChangeHint(); + CalculateCumulativeChangeHint(aStyleContext); if (mTarget) { EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); if (effectSet) { effectSet->MarkCascadeNeedsUpdate(); } @@ -1466,24 +1466,71 @@ KeyframeEffectReadOnly::SetPerformanceWa nsAutoCString logMessage = NS_ConvertUTF16toUTF8(localizedString); AnimationUtils::LogAsyncAnimationFailure(logMessage, mTarget->mElement); } return; } } } +static already_AddRefed<nsStyleContext> +CreateStyleContextForAnimationValue(nsCSSProperty aProperty, + StyleAnimationValue aValue, + nsStyleContext* aBaseStyleContext) +{ + MOZ_ASSERT(aBaseStyleContext, + "CreateStyleContextForAnimationValue needs to be called " + "with a valid nsStyleContext"); + + RefPtr<AnimValuesStyleRule> styleRule = new AnimValuesStyleRule(); + styleRule->AddValue(aProperty, aValue); + + nsCOMArray<nsIStyleRule> rules; + rules.AppendObject(styleRule); + + MOZ_ASSERT(aBaseStyleContext->PresContext()->StyleSet()->IsGecko(), + "ServoStyleSet should not use StyleAnimationValue for animations"); + nsStyleSet* styleSet = + aBaseStyleContext->PresContext()->StyleSet()->AsGecko(); + + RefPtr<nsStyleContext> styleContext = + styleSet->ResolveStyleByAddingRules(aBaseStyleContext, rules); + + // We need to call StyleData to generate cached data for the style context. + // Otherwise CalcStyleDifference returns no meaningful result. + styleContext->StyleData(nsCSSProps::kSIDTable[aProperty]); + + return styleContext.forget(); +} + void -KeyframeEffectReadOnly::CalculateCumulativeChangeHint() +KeyframeEffectReadOnly::CalculateCumulativeChangeHint( + nsStyleContext *aStyleContext) { mCumulativeChangeHint = nsChangeHint(0); for (const AnimationProperty& property : mProperties) { for (const AnimationPropertySegment& segment : property.mSegments) { - mCumulativeChangeHint |= segment.mChangeHint; + RefPtr<nsStyleContext> fromContext = + CreateStyleContextForAnimationValue(property.mProperty, + segment.mFromValue, aStyleContext); + + RefPtr<nsStyleContext> toContext = + CreateStyleContextForAnimationValue(property.mProperty, + segment.mToValue, aStyleContext); + + uint32_t equalStructs = 0; + uint32_t samePointerStructs = 0; + nsChangeHint changeHint = + fromContext->CalcStyleDifference(toContext, + nsChangeHint(0), + &equalStructs, + &samePointerStructs); + + mCumulativeChangeHint |= changeHint; } } } bool KeyframeEffectReadOnly::CanIgnoreIfNotVisible() const { if (!AnimationUtils::IsOffscreenThrottlingEnabled()) {
--- a/dom/animation/KeyframeEffect.h +++ b/dom/animation/KeyframeEffect.h @@ -115,18 +115,16 @@ struct Keyframe }; struct AnimationPropertySegment { float mFromKey, mToKey; StyleAnimationValue mFromValue, mToValue; Maybe<ComputedTimingFunction> mTimingFunction; - nsChangeHint mChangeHint; - bool operator==(const AnimationPropertySegment& aOther) const { return mFromKey == aOther.mFromKey && mToKey == aOther.mToKey && mFromValue == aOther.mFromValue && mToValue == aOther.mToValue && mTimingFunction == aOther.mTimingFunction; } bool operator!=(const AnimationPropertySegment& aOther) const { @@ -342,17 +340,17 @@ public: // compositor. |aParams| and |aParamsLength| are optional parameters which // will be used to generate a localized message for devtools. void SetPerformanceWarning( nsCSSProperty aProperty, const AnimationPerformanceWarning& aWarning); // Cumulative change hint on each segment for each property. // This is used for deciding the animation is paint-only. - void CalculateCumulativeChangeHint(); + void CalculateCumulativeChangeHint(nsStyleContext* aStyleContext); // Returns true if all of animation properties' change hints // can ignore painting if the animation is not visible. // See nsChangeHint_Hints_CanIgnoreIfNotVisible in nsChangeHint.h // in detail which change hint can be ignored. bool CanIgnoreIfNotVisible() const; protected:
--- a/dom/animation/KeyframeUtils.cpp +++ b/dom/animation/KeyframeUtils.cpp @@ -1069,46 +1069,16 @@ MarkAsComputeValuesFailureKey(PropertyVa static bool IsComputeValuesFailureKey(const PropertyValuePair& aPair) { return nsCSSProps::IsShorthand(aPair.mProperty) && aPair.mValue.GetTokenStreamValue()->mPropertyID == eCSSPropertyExtra_no_properties; } -static already_AddRefed<nsStyleContext> -CreateStyleContextForAnimationValue(nsCSSProperty aProperty, - StyleAnimationValue aValue, - nsStyleContext* aBaseStyleContext) -{ - MOZ_ASSERT(aBaseStyleContext, - "CreateStyleContextForAnimationValue needs to be called " - "with a valid nsStyleContext"); - - RefPtr<AnimValuesStyleRule> styleRule = new AnimValuesStyleRule(); - styleRule->AddValue(aProperty, aValue); - - nsCOMArray<nsIStyleRule> rules; - rules.AppendObject(styleRule); - - MOZ_ASSERT(aBaseStyleContext->PresContext()->StyleSet()->IsGecko(), - "ServoStyleSet should not use StyleAnimationValue for animations"); - nsStyleSet* styleSet = - aBaseStyleContext->PresContext()->StyleSet()->AsGecko(); - - RefPtr<nsStyleContext> styleContext = - styleSet->ResolveStyleByAddingRules(aBaseStyleContext, rules); - - // We need to call StyleData to generate cached data for the style context. - // Otherwise CalcStyleDifference returns no meaningful result. - styleContext->StyleData(nsCSSProps::kSIDTable[aProperty]); - - return styleContext.forget(); -} - /** * Builds an array of AnimationProperty objects to represent the keyframe * animation segments in aEntries. */ static void BuildSegmentsFromValueEntries(nsStyleContext* aStyleContext, nsTArray<KeyframeValueEntry>& aEntries, nsTArray<AnimationProperty>& aResult) @@ -1238,32 +1208,16 @@ BuildSegmentsFromValueEntries(nsStyleCon AnimationPropertySegment* segment = animationProperty->mSegments.AppendElement(); segment->mFromKey = aEntries[i].mOffset; segment->mToKey = aEntries[j].mOffset; segment->mFromValue = aEntries[i].mValue; segment->mToValue = aEntries[j].mValue; segment->mTimingFunction = aEntries[i].mTimingFunction; - RefPtr<nsStyleContext> fromContext = - CreateStyleContextForAnimationValue(animationProperty->mProperty, - segment->mFromValue, aStyleContext); - - RefPtr<nsStyleContext> toContext = - CreateStyleContextForAnimationValue(animationProperty->mProperty, - segment->mToValue, aStyleContext); - - uint32_t equalStructs = 0; - uint32_t samePointerStructs = 0; - segment->mChangeHint = - fromContext->CalcStyleDifference(toContext, - nsChangeHint(0), - &equalStructs, - &samePointerStructs); - i = j; } } /** * Converts a JS object representing a property-indexed keyframe into * an array of Keyframe objects. *
--- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -9234,44 +9234,29 @@ nsContentUtils::GetPresentationURL(nsIDo } topFrameElement->GetAttribute(NS_LITERAL_STRING("mozpresentation"), aPresentationUrl); } /* static */ nsIDocShell* nsContentUtils::GetDocShellForEventTarget(EventTarget* aTarget) { - nsCOMPtr<nsINode> node(do_QueryInterface(aTarget)); - nsIDocument* doc = nullptr; - nsIDocShell* docShell = nullptr; - - if (node) { - doc = node->OwnerDoc(); - if (!doc->GetDocShell()) { - bool ignore; - nsCOMPtr<nsPIDOMWindowInner> window = - do_QueryInterface(doc->GetScriptHandlingObject(ignore)); - if (window) { - doc = window->GetExtantDoc(); - } - } + nsCOMPtr<nsPIDOMWindowInner> innerWindow; + + if (nsCOMPtr<nsINode> node = do_QueryInterface(aTarget)) { + bool ignore; + innerWindow = + do_QueryInterface(node->OwnerDoc()->GetScriptHandlingObject(ignore)); + } else if ((innerWindow = do_QueryInterface(aTarget))) { + // Nothing else to do } else { - nsCOMPtr<nsPIDOMWindowInner> window(do_QueryInterface(aTarget)); - if (window) { - doc = window->GetExtantDoc(); - } - } - - if (!doc) { - nsCOMPtr<DOMEventTargetHelper> helper(do_QueryInterface(aTarget)); + nsCOMPtr<DOMEventTargetHelper> helper = do_QueryInterface(aTarget); if (helper) { - if (nsPIDOMWindowInner* window = helper->GetOwner()) { - doc = window->GetExtantDoc(); - } - } - } - - if (doc) { - docShell = doc->GetDocShell(); - } - - return docShell; -} + innerWindow = helper->GetOwner(); + } + } + + if (innerWindow) { + return innerWindow->GetDocShell(); + } + + return nullptr; +}
--- a/dom/base/nsFrameMessageManager.cpp +++ b/dom/base/nsFrameMessageManager.cpp @@ -1658,29 +1658,26 @@ NS_NewGlobalMessageManager(nsIMessageBro MM_CHROME | MM_GLOBAL | MM_BROADCASTER); RegisterStrongMemoryReporter(new MessageManagerReporter()); mm.forget(aResult); return NS_OK; } nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>* nsMessageManagerScriptExecutor::sCachedScripts = nullptr; -nsScriptCacheCleaner* nsMessageManagerScriptExecutor::sScriptCacheCleaner = nullptr; +StaticRefPtr<nsScriptCacheCleaner> nsMessageManagerScriptExecutor::sScriptCacheCleaner; void nsMessageManagerScriptExecutor::DidCreateGlobal() { NS_ASSERTION(mGlobal, "Should have mGlobal!"); if (!sCachedScripts) { sCachedScripts = new nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>; - - RefPtr<nsScriptCacheCleaner> scriptCacheCleaner = - new nsScriptCacheCleaner(); - scriptCacheCleaner.forget(&sScriptCacheCleaner); + sScriptCacheCleaner = new nsScriptCacheCleaner(); } } // static void nsMessageManagerScriptExecutor::PurgeCache() { if (sCachedScripts) { @@ -1696,19 +1693,17 @@ nsMessageManagerScriptExecutor::PurgeCac void nsMessageManagerScriptExecutor::Shutdown() { if (sCachedScripts) { PurgeCache(); delete sCachedScripts; sCachedScripts = nullptr; - - RefPtr<nsScriptCacheCleaner> scriptCacheCleaner; - scriptCacheCleaner.swap(sScriptCacheCleaner); + sScriptCacheCleaner = nullptr; } } void nsMessageManagerScriptExecutor::LoadScriptInternal(const nsAString& aURL, bool aRunInGlobalScope) { if (!mGlobal || !sCachedScripts) {
--- a/dom/base/nsFrameMessageManager.h +++ b/dom/base/nsFrameMessageManager.h @@ -16,16 +16,17 @@ #include "nsIAtom.h" #include "nsCycleCollectionParticipant.h" #include "nsTArray.h" #include "nsIPrincipal.h" #include "nsIXPConnect.h" #include "nsDataHashtable.h" #include "nsClassHashtable.h" #include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" #include "nsIObserverService.h" #include "nsThreadUtils.h" #include "nsWeakPtr.h" #include "mozilla/Attributes.h" #include "js/RootingAPI.h" #include "nsTObserverArray.h" #include "mozilla/dom/SameProcessMessageQueue.h" #include "mozilla/dom/ipc/StructuredCloneData.h" @@ -401,17 +402,17 @@ protected: bool aRunInGlobalScope); bool InitChildGlobalInternal(nsISupports* aScope, const nsACString& aID); void Trace(const TraceCallbacks& aCallbacks, void* aClosure); nsCOMPtr<nsIXPConnectJSObjectHolder> mGlobal; nsCOMPtr<nsIPrincipal> mPrincipal; AutoTArray<JS::Heap<JSObject*>, 2> mAnonymousGlobalScopes; static nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>* sCachedScripts; - static nsScriptCacheCleaner* sScriptCacheCleaner; + static mozilla::StaticRefPtr<nsScriptCacheCleaner> sScriptCacheCleaner; }; class nsScriptCacheCleaner final : public nsIObserver { ~nsScriptCacheCleaner() {} NS_DECL_ISUPPORTS
--- a/dom/base/nsHostObjectProtocolHandler.cpp +++ b/dom/base/nsHostObjectProtocolHandler.cpp @@ -20,26 +20,56 @@ #include "nsContentUtils.h" #include "nsError.h" #include "nsHostObjectURI.h" #include "nsIMemoryReporter.h" #include "nsIPrincipal.h" #include "nsIUUIDGenerator.h" #include "nsNetUtil.h" +using mozilla::DOMMediaStream; using mozilla::dom::BlobImpl; +using mozilla::dom::MediaSource; using mozilla::ErrorResult; using mozilla::net::LoadInfo; // ----------------------------------------------------------------------- // Hash table struct DataInfo { - // mObject is expected to be an BlobImpl, DOMMediaStream, or MediaSource - nsCOMPtr<nsISupports> mObject; + enum ObjectType { + eBlobImpl, + eMediaStream, + eMediaSource + }; + + DataInfo(BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal) + : mObjectType(eBlobImpl) + , mBlobImpl(aBlobImpl) + , mPrincipal(aPrincipal) + {} + + DataInfo(DOMMediaStream* aMediaStream, nsIPrincipal* aPrincipal) + : mObjectType(eMediaStream) + , mMediaStream(aMediaStream) + , mPrincipal(aPrincipal) + {} + + DataInfo(MediaSource* aMediaSource, nsIPrincipal* aPrincipal) + : mObjectType(eMediaSource) + , mMediaSource(aMediaSource) + , mPrincipal(aPrincipal) + {} + + ObjectType mObjectType; + + RefPtr<BlobImpl> mBlobImpl; + RefPtr<DOMMediaStream> mMediaStream; + RefPtr<MediaSource> mMediaSource; + nsCOMPtr<nsIPrincipal> mPrincipal; nsCString mStack; }; static nsClassHashtable<nsCStringHashKey, DataInfo>* gDataTable; static DataInfo* GetDataInfo(const nsACString& aUri) @@ -66,16 +96,31 @@ GetDataInfo(const nsACString& aUri) if (pos < 0) { gDataTable->Get(aUri, &res); } else { gDataTable->Get(StringHead(aUri, pos), &res); } return res; } +static DataInfo* +GetDataInfoFromURI(nsIURI* aURI) +{ + if (!aURI) { + return nullptr; + } + + nsCString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + return GetDataInfo(spec); +} // Memory reporting for the hash table. namespace mozilla { void BroadcastBlobURLRegistration(const nsACString& aURI, BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal) @@ -145,28 +190,34 @@ class BlobURLsReporter final : public ns if (!gDataTable) { return NS_OK; } nsDataHashtable<nsPtrHashKey<BlobImpl>, uint32_t> refCounts; // Determine number of URLs per BlobImpl, to handle the case where it's > 1. for (auto iter = gDataTable->Iter(); !iter.Done(); iter.Next()) { - nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(iter.UserData()->mObject); - if (blobImpl) { - refCounts.Put(blobImpl, refCounts.Get(blobImpl) + 1); + if (iter.UserData()->mObjectType != DataInfo::eBlobImpl) { + continue; } + + BlobImpl* blobImpl = iter.UserData()->mBlobImpl; + MOZ_ASSERT(blobImpl); + + refCounts.Put(blobImpl, refCounts.Get(blobImpl) + 1); } for (auto iter = gDataTable->Iter(); !iter.Done(); iter.Next()) { nsCStringHashKey::KeyType key = iter.Key(); DataInfo* info = iter.UserData(); - nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(iter.UserData()->mObject); - if (blobImpl) { + if (iter.UserData()->mObjectType == DataInfo::eBlobImpl) { + BlobImpl* blobImpl = iter.UserData()->mBlobImpl; + MOZ_ASSERT(blobImpl); + NS_NAMED_LITERAL_CSTRING(desc, "A blob URL allocated with URL.createObjectURL; the referenced " "blob cannot be freed until all URLs for it have been explicitly " "invalidated with URL.revokeObjectURL."); nsAutoCString path, url, owner, specialDesc; nsCOMPtr<nsIURI> principalURI; uint64_t size = 0; uint32_t refCount = 1; @@ -229,37 +280,32 @@ class BlobURLsReporter final : public ns aCallback->Callback(EmptyCString(), path, KIND_OTHER, UNITS_COUNT, 1, descString, aData); } - } else { - // Just report the path for the DOMMediaStream or MediaSource. - nsCOMPtr<mozilla::dom::MediaSource> - ms(do_QueryInterface(info->mObject)); - nsAutoCString path; - path = ms ? "media-source-urls/" : "dom-media-stream-urls/"; - BuildPath(path, key, info, aAnonymize); + continue; + } - NS_NAMED_LITERAL_CSTRING(desc, - "An object URL allocated with URL.createObjectURL; the referenced " - "data cannot be freed until all URLs for it have been explicitly " - "invalidated with URL.revokeObjectURL."); + // Just report the path for the DOMMediaStream or MediaSource. + nsAutoCString path; + path = iter.UserData()->mObjectType == DataInfo::eMediaSource + ? "media-source-urls/" : "dom-media-stream-urls/"; + BuildPath(path, key, info, aAnonymize); - aCallback->Callback(EmptyCString(), - path, - KIND_OTHER, - UNITS_COUNT, - 1, - desc, - aData); - } + NS_NAMED_LITERAL_CSTRING(desc, + "An object URL allocated with URL.createObjectURL; the referenced " + "data cannot be freed until all URLs for it have been explicitly " + "invalidated with URL.revokeObjectURL."); + + aCallback->Callback(EmptyCString(), path, KIND_OTHER, UNITS_COUNT, 1, + desc, aData); } return NS_OK; } // Initialize info->mStack to record JS stack info, if enabled. // The string generated here is used in ReportCallback, below. static void GetJSStackForBlob(DataInfo* aInfo) @@ -365,16 +411,32 @@ class BlobURLsReporter final : public ns } } }; NS_IMPL_ISUPPORTS(BlobURLsReporter, nsIMemoryReporter) } // namespace mozilla +template<typename T> +static nsresult +AddDataEntryInternal(const nsACString& aURI, T aObject, + nsIPrincipal* aPrincipal) +{ + if (!gDataTable) { + gDataTable = new nsClassHashtable<nsCStringHashKey, DataInfo>; + } + + DataInfo* info = new DataInfo(aObject, aPrincipal); + mozilla::BlobURLsReporter::GetJSStackForBlob(info); + + gDataTable->Put(aURI, info); + return NS_OK; +} + void nsHostObjectProtocolHandler::Init(void) { static bool initialized = false; if (!initialized) { initialized = true; RegisterStrongMemoryReporter(new mozilla::HostObjectURLsReporter()); @@ -382,88 +444,93 @@ nsHostObjectProtocolHandler::Init(void) } } nsHostObjectProtocolHandler::nsHostObjectProtocolHandler() { Init(); } -nsresult -nsHostObjectProtocolHandler::AddDataEntry(const nsACString& aScheme, - nsISupports* aObject, +/* static */ nsresult +nsHostObjectProtocolHandler::AddDataEntry(BlobImpl* aBlobImpl, + nsIPrincipal* aPrincipal, + nsACString& aUri) +{ + Init(); + + nsresult rv = GenerateURIStringForBlobURL(aPrincipal, aUri); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AddDataEntryInternal(aUri, aBlobImpl, aPrincipal); + NS_ENSURE_SUCCESS(rv, rv); + + BroadcastBlobURLRegistration(aUri, aBlobImpl, aPrincipal); + return NS_OK; +} + +/* static */ nsresult +nsHostObjectProtocolHandler::AddDataEntry(DOMMediaStream* aMediaStream, nsIPrincipal* aPrincipal, nsACString& aUri) { -#ifdef DEBUG - { - nsCOMPtr<BlobImpl> blobImpl(do_QueryInterface(aObject)); - nsCOMPtr<MediaSource> mediaSource(do_QueryInterface(aObject)); - nsCOMPtr<DOMMediaStream> mediaStream(do_QueryInterface(aObject)); - - // We support only these types. - MOZ_ASSERT(blobImpl || mediaSource || mediaStream); - } -#endif - Init(); - nsresult rv = GenerateURIString(aScheme, aPrincipal, aUri); + nsresult rv = GenerateURIStringForBlobURL(aPrincipal, aUri); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AddDataEntryInternal(aUri, aMediaStream, aPrincipal); NS_ENSURE_SUCCESS(rv, rv); - rv = AddDataEntry(aUri, aObject, aPrincipal); + return NS_OK; +} + +/* static */ nsresult +nsHostObjectProtocolHandler::AddDataEntry(MediaSource* aMediaSource, + nsIPrincipal* aPrincipal, + nsACString& aUri) +{ + Init(); + + nsresult rv = GenerateURIStringForBlobURL(aPrincipal, aUri); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(aObject); - if (blobImpl) { - BroadcastBlobURLRegistration(aUri, blobImpl, aPrincipal); - } + rv = AddDataEntryInternal(aUri, aMediaSource, aPrincipal); + NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } /* static */ nsresult nsHostObjectProtocolHandler::AddDataEntry(const nsACString& aURI, - nsISupports* aObject, - nsIPrincipal* aPrincipal) + nsIPrincipal* aPrincipal, + mozilla::dom::BlobImpl* aBlobImpl) { - if (!gDataTable) { - gDataTable = new nsClassHashtable<nsCStringHashKey, DataInfo>; - } - - DataInfo* info = new DataInfo; - - info->mObject = aObject; - info->mPrincipal = aPrincipal; - mozilla::BlobURLsReporter::GetJSStackForBlob(info); - - gDataTable->Put(aURI, info); - return NS_OK; + return AddDataEntryInternal(aURI, aBlobImpl, aPrincipal); } /* static */ bool nsHostObjectProtocolHandler::GetAllBlobURLEntries(nsTArray<BlobURLRegistrationData>& aRegistrations, ContentParent* aCP) { MOZ_ASSERT(aCP); if (!gDataTable) { return true; } for (auto iter = gDataTable->ConstIter(); !iter.Done(); iter.Next()) { DataInfo* info = iter.UserData(); MOZ_ASSERT(info); - nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(info->mObject); - if (!blobImpl) { + if (info->mObjectType != DataInfo::eBlobImpl) { continue; } - PBlobParent* blobParent = aCP->GetOrCreateActorForBlobImpl(blobImpl); + MOZ_ASSERT(info->mBlobImpl); + PBlobParent* blobParent = aCP->GetOrCreateActorForBlobImpl(info->mBlobImpl); if (!blobParent) { return false; } aRegistrations.AppendElement( BlobURLRegistrationData(nsCString(iter.Key()), blobParent, nullptr, IPC::Principal(info->mPrincipal))); } @@ -479,21 +546,18 @@ nsHostObjectProtocolHandler::RemoveDataE return; } DataInfo* info = GetDataInfo(aUri); if (!info) { return; } - if (aBroadcastToOtherProcesses) { - nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(info->mObject); - if (blobImpl) { - BroadcastBlobURLUnregistration(aUri, info); - } + if (aBroadcastToOtherProcesses && info->mObjectType == DataInfo::eBlobImpl) { + BroadcastBlobURLUnregistration(aUri, info); } gDataTable->Remove(aUri); if (gDataTable->Count() == 0) { delete gDataTable; gDataTable = nullptr; } } @@ -543,16 +607,24 @@ nsHostObjectProtocolHandler::GenerateURI aUri.Append('/'); } aUri += Substring(chars + 1, chars + NSID_LENGTH - 2); return NS_OK; } +nsresult +nsHostObjectProtocolHandler::GenerateURIStringForBlobURL(nsIPrincipal* aPrincipal, + nsACString& aUri) +{ + return + GenerateURIString(NS_LITERAL_CSTRING(BLOBURI_SCHEME), aPrincipal, aUri); +} + nsIPrincipal* nsHostObjectProtocolHandler::GetDataEntryPrincipal(const nsACString& aUri) { if (!gDataTable) { return nullptr; } DataInfo* res = GetDataInfo(aUri); @@ -573,33 +645,24 @@ nsHostObjectProtocolHandler::Traverse(co } DataInfo* res; gDataTable->Get(aUri, &res); if (!res) { return; } - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mObject"); - aCallback.NoteXPCOMChild(res->mObject); -} + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mBlobImpl"); + aCallback.NoteXPCOMChild(res->mBlobImpl); -static nsISupports* -GetDataObjectForSpec(const nsACString& aSpec) -{ - DataInfo* info = GetDataInfo(aSpec); - return info ? info->mObject : nullptr; -} + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mMediaSource"); + aCallback.NoteXPCOMChild(res->mMediaSource); -static nsISupports* -GetDataObject(nsIURI* aURI) -{ - nsCString spec; - aURI->GetSpec(spec); - return GetDataObjectForSpec(spec); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mMediaStream"); + aCallback.NoteXPCOMChild(res->mMediaStream); } // ----------------------------------------------------------------------- // Protocol handler NS_IMPL_ISUPPORTS(nsHostObjectProtocolHandler, nsIProtocolHandler) NS_IMETHODIMP @@ -624,19 +687,19 @@ nsHostObjectProtocolHandler::NewURI(cons nsIURI **aResult) { *aResult = nullptr; nsresult rv; DataInfo* info = GetDataInfo(aSpec); RefPtr<nsHostObjectURI> uri; - if (info) { - nsCOMPtr<BlobImpl> blob = do_QueryInterface(info->mObject); - uri = new nsHostObjectURI(info->mPrincipal, blob); + if (info && info->mObjectType == DataInfo::eBlobImpl) { + MOZ_ASSERT(info->mBlobImpl); + uri = new nsHostObjectURI(info->mPrincipal, info->mBlobImpl); } else { uri = new nsHostObjectURI(nullptr, nullptr); } rv = uri->SetSpec(aSpec); NS_ENSURE_SUCCESS(rv, rv); NS_TryToSetImmutable(uri); @@ -660,20 +723,17 @@ nsHostObjectProtocolHandler::NewChannel2 nsCOMPtr<nsISupports> tmp; MOZ_ALWAYS_SUCCEEDS(uriBlobImpl->GetBlobImpl(getter_AddRefs(tmp))); nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(tmp); if (!blobImpl) { return NS_ERROR_DOM_BAD_URI; } #ifdef DEBUG - nsCString spec; - uri->GetSpec(spec); - - DataInfo* info = GetDataInfo(spec); + DataInfo* info = GetDataInfoFromURI(uri); // Info can be null, in case this blob URL has been revoked already. if (info) { nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(uri); nsCOMPtr<nsIPrincipal> principal; uriPrinc->GetPrincipal(getter_AddRefs(principal)); NS_ASSERTION(info->mPrincipal == principal, "Wrong principal!"); } @@ -738,62 +798,50 @@ nsHostObjectProtocolHandler::AllowPort(i NS_IMETHODIMP nsBlobProtocolHandler::GetScheme(nsACString &result) { result.AssignLiteral(BLOBURI_SCHEME); return NS_OK; } NS_IMETHODIMP -nsMediaStreamProtocolHandler::GetScheme(nsACString &result) -{ - result.AssignLiteral(MEDIASTREAMURI_SCHEME); - return NS_OK; -} - -NS_IMETHODIMP -nsMediaSourceProtocolHandler::GetScheme(nsACString &result) -{ - result.AssignLiteral(MEDIASOURCEURI_SCHEME); - return NS_OK; -} - -NS_IMETHODIMP nsFontTableProtocolHandler::GetScheme(nsACString &result) { result.AssignLiteral(FONTTABLEURI_SCHEME); return NS_OK; } nsresult NS_GetBlobForBlobURI(nsIURI* aURI, BlobImpl** aBlob) { NS_ASSERTION(IsBlobURI(aURI), "Only call this with blob URIs"); *aBlob = nullptr; - nsCOMPtr<BlobImpl> blob = do_QueryInterface(GetDataObject(aURI)); - if (!blob) { + DataInfo* info = GetDataInfoFromURI(aURI); + if (!info || info->mObjectType != DataInfo::eBlobImpl) { return NS_ERROR_DOM_BAD_URI; } + RefPtr<BlobImpl> blob = info->mBlobImpl; blob.forget(aBlob); return NS_OK; } nsresult NS_GetBlobForBlobURISpec(const nsACString& aSpec, BlobImpl** aBlob) { *aBlob = nullptr; - nsCOMPtr<BlobImpl> blob = do_QueryInterface(GetDataObjectForSpec(aSpec)); - if (!blob) { + DataInfo* info = GetDataInfo(aSpec); + if (!info || info->mObjectType != DataInfo::eBlobImpl) { return NS_ERROR_DOM_BAD_URI; } + RefPtr<BlobImpl> blob = info->mBlobImpl; blob.forget(aBlob); return NS_OK; } nsresult NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream) { RefPtr<BlobImpl> blobImpl; @@ -811,23 +859,24 @@ NS_GetStreamForBlobURI(nsIURI* aURI, nsI return NS_OK; } nsresult NS_GetStreamForMediaStreamURI(nsIURI* aURI, mozilla::DOMMediaStream** aStream) { NS_ASSERTION(IsMediaStreamURI(aURI), "Only call this with mediastream URIs"); - nsISupports* dataObject = GetDataObject(aURI); - if (!dataObject) { + DataInfo* info = GetDataInfoFromURI(aURI); + if (!info || info->mObjectType != DataInfo::eMediaStream) { return NS_ERROR_DOM_BAD_URI; } - *aStream = nullptr; - return CallQueryInterface(dataObject, aStream); + RefPtr<DOMMediaStream> mediaStream = info->mMediaStream; + mediaStream.forget(aStream); + return NS_OK; } NS_IMETHODIMP nsFontTableProtocolHandler::NewURI(const nsACString& aSpec, const char *aCharset, nsIURI *aBaseURI, nsIURI **aResult) { @@ -860,67 +909,76 @@ nsFontTableProtocolHandler::NewURI(const nsresult NS_GetSourceForMediaSourceURI(nsIURI* aURI, mozilla::dom::MediaSource** aSource) { NS_ASSERTION(IsMediaSourceURI(aURI), "Only call this with mediasource URIs"); *aSource = nullptr; - nsCOMPtr<mozilla::dom::MediaSource> source = do_QueryInterface(GetDataObject(aURI)); - if (!source) { + DataInfo* info = GetDataInfoFromURI(aURI); + if (!info || info->mObjectType != DataInfo::eMediaSource) { return NS_ERROR_DOM_BAD_URI; } - source.forget(aSource); + RefPtr<MediaSource> mediaSource = info->mMediaSource; + mediaSource.forget(aSource); return NS_OK; } #define NS_BLOBPROTOCOLHANDLER_CID \ { 0xb43964aa, 0xa078, 0x44b2, \ { 0xb0, 0x6b, 0xfd, 0x4d, 0x1b, 0x17, 0x2e, 0x66 } } -#define NS_MEDIASTREAMPROTOCOLHANDLER_CID \ -{ 0x27d1fa24, 0x2b73, 0x4db3, \ - { 0xab, 0x48, 0xb9, 0x83, 0x83, 0x40, 0xe0, 0x81 } } - -#define NS_MEDIASOURCEPROTOCOLHANDLER_CID \ -{ 0x12ef31fc, 0xa8fb, 0x4661, \ - { 0x9a, 0x63, 0xfb, 0x61, 0x04,0x5d, 0xb8, 0x61 } } - #define NS_FONTTABLEPROTOCOLHANDLER_CID \ { 0x3fc8f04e, 0xd719, 0x43ca, \ { 0x9a, 0xd0, 0x18, 0xee, 0x32, 0x02, 0x11, 0xf2 } } NS_GENERIC_FACTORY_CONSTRUCTOR(nsBlobProtocolHandler) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsMediaStreamProtocolHandler) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsMediaSourceProtocolHandler) NS_GENERIC_FACTORY_CONSTRUCTOR(nsFontTableProtocolHandler) NS_DEFINE_NAMED_CID(NS_BLOBPROTOCOLHANDLER_CID); -NS_DEFINE_NAMED_CID(NS_MEDIASTREAMPROTOCOLHANDLER_CID); -NS_DEFINE_NAMED_CID(NS_MEDIASOURCEPROTOCOLHANDLER_CID); NS_DEFINE_NAMED_CID(NS_FONTTABLEPROTOCOLHANDLER_CID); static const mozilla::Module::CIDEntry kHostObjectProtocolHandlerCIDs[] = { { &kNS_BLOBPROTOCOLHANDLER_CID, false, nullptr, nsBlobProtocolHandlerConstructor }, - { &kNS_MEDIASTREAMPROTOCOLHANDLER_CID, false, nullptr, nsMediaStreamProtocolHandlerConstructor }, - { &kNS_MEDIASOURCEPROTOCOLHANDLER_CID, false, nullptr, nsMediaSourceProtocolHandlerConstructor }, { &kNS_FONTTABLEPROTOCOLHANDLER_CID, false, nullptr, nsFontTableProtocolHandlerConstructor }, { nullptr } }; static const mozilla::Module::ContractIDEntry kHostObjectProtocolHandlerContracts[] = { { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX BLOBURI_SCHEME, &kNS_BLOBPROTOCOLHANDLER_CID }, - { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX MEDIASTREAMURI_SCHEME, &kNS_MEDIASTREAMPROTOCOLHANDLER_CID }, - { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX MEDIASOURCEURI_SCHEME, &kNS_MEDIASOURCEPROTOCOLHANDLER_CID }, { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX FONTTABLEURI_SCHEME, &kNS_FONTTABLEPROTOCOLHANDLER_CID }, { nullptr } }; static const mozilla::Module kHostObjectProtocolHandlerModule = { mozilla::Module::kVersion, kHostObjectProtocolHandlerCIDs, kHostObjectProtocolHandlerContracts }; NSMODULE_DEFN(HostObjectProtocolHandler) = &kHostObjectProtocolHandlerModule; +bool IsType(nsIURI* aUri, DataInfo::ObjectType aType) +{ + DataInfo* info = GetDataInfoFromURI(aUri); + if (!info) { + return false; + } + + return info->mObjectType == aType; +} + +bool IsBlobURI(nsIURI* aUri) +{ + return IsType(aUri, DataInfo::eBlobImpl); +} + +bool IsMediaStreamURI(nsIURI* aUri) +{ + return IsType(aUri, DataInfo::eMediaStream); +} + +bool IsMediaSourceURI(nsIURI* aUri) +{ + return IsType(aUri, DataInfo::eMediaSource); +}
--- a/dom/base/nsHostObjectProtocolHandler.h +++ b/dom/base/nsHostObjectProtocolHandler.h @@ -10,24 +10,23 @@ #include "mozilla/Attributes.h" #include "nsIProtocolHandler.h" #include "nsIURI.h" #include "nsCOMPtr.h" #include "nsIInputStream.h" #include "nsTArray.h" #define BLOBURI_SCHEME "blob" -#define MEDIASTREAMURI_SCHEME "mediastream" -#define MEDIASOURCEURI_SCHEME "mediasource" #define FONTTABLEURI_SCHEME "moz-fonttable" #define RTSPURI_SCHEME "rtsp" class nsIPrincipal; namespace mozilla { +class BlobURLsReporter; class DOMMediaStream; namespace dom { class BlobImpl; class BlobURLRegistrationData; class ContentParent; class MediaSource; } // namespace dom } // namespace mozilla @@ -46,36 +45,44 @@ public: NS_IMETHOD NewChannel2(nsIURI *aURI, nsILoadInfo *aLoadinfo, nsIChannel * *_retval) override; NS_IMETHOD NewChannel(nsIURI *aURI, nsIChannel * *_retval) override; NS_IMETHOD AllowPort(int32_t port, const char * scheme, bool *_retval) override; // If principal is not null, its origin will be used to generate the URI. static nsresult GenerateURIString(const nsACString &aScheme, nsIPrincipal* aPrincipal, nsACString &aUri); + static nsresult GenerateURIStringForBlobURL(nsIPrincipal* aPrincipal, + nsACString &aUri); // Methods for managing uri->object mapping // AddDataEntry creates the URI with the given scheme and returns it in aUri - static nsresult AddDataEntry(const nsACString& aScheme, - nsISupports* aObject, + static nsresult AddDataEntry(mozilla::dom::BlobImpl* aBlobImpl, + nsIPrincipal* aPrincipal, + nsACString& aUri); + static nsresult AddDataEntry(mozilla::DOMMediaStream* aMediaStream, nsIPrincipal* aPrincipal, nsACString& aUri); + static nsresult AddDataEntry(mozilla::dom::MediaSource* aMediaSource, + nsIPrincipal* aPrincipal, + nsACString& aUri); + // IPC only + static nsresult AddDataEntry(const nsACString& aURI, + nsIPrincipal* aPrincipal, + mozilla::dom::BlobImpl* aBlobImpl); + static void RemoveDataEntry(const nsACString& aUri, bool aBroadcastToOTherProcesses = true); // This is for IPC only. static void RemoveDataEntries(); static nsIPrincipal* GetDataEntryPrincipal(const nsACString& aUri); static void Traverse(const nsACString& aUri, nsCycleCollectionTraversalCallback& aCallback); - // IPC or internal use only - static nsresult AddDataEntry(const nsACString& aURI, - nsISupports* aObject, - nsIPrincipal* aPrincipal); static bool GetAllBlobURLEntries(nsTArray<mozilla::dom::BlobURLRegistrationData>& aRegistrations, mozilla::dom::ContentParent* aCP); protected: virtual ~nsHostObjectProtocolHandler() {} private: @@ -83,59 +90,39 @@ private: }; class nsBlobProtocolHandler : public nsHostObjectProtocolHandler { public: NS_IMETHOD GetScheme(nsACString &result) override; }; -class nsMediaStreamProtocolHandler : public nsHostObjectProtocolHandler -{ -public: - NS_IMETHOD GetScheme(nsACString &result) override; -}; - class nsMediaSourceProtocolHandler : public nsHostObjectProtocolHandler { public: NS_IMETHOD GetScheme(nsACString &result) override; }; class nsFontTableProtocolHandler : public nsHostObjectProtocolHandler { public: NS_IMETHOD GetScheme(nsACString &result); NS_IMETHOD NewURI(const nsACString & aSpec, const char * aOriginCharset, nsIURI *aBaseURI, nsIURI * *_retval); }; -inline bool IsBlobURI(nsIURI* aUri) -{ - bool isBlob; - return NS_SUCCEEDED(aUri->SchemeIs(BLOBURI_SCHEME, &isBlob)) && isBlob; -} +bool IsBlobURI(nsIURI* aUri); +bool IsMediaStreamURI(nsIURI* aUri); +bool IsMediaSourceURI(nsIURI* aUri); inline bool IsRtspURI(nsIURI* aUri) { bool isRtsp; return NS_SUCCEEDED(aUri->SchemeIs(RTSPURI_SCHEME, &isRtsp)) && isRtsp; } -inline bool IsMediaStreamURI(nsIURI* aUri) -{ - bool isStream; - return NS_SUCCEEDED(aUri->SchemeIs(MEDIASTREAMURI_SCHEME, &isStream)) && isStream; -} - -inline bool IsMediaSourceURI(nsIURI* aUri) -{ - bool isMediaSource; - return NS_SUCCEEDED(aUri->SchemeIs(MEDIASOURCEURI_SCHEME, &isMediaSource)) && isMediaSource; -} - inline bool IsFontTableURI(nsIURI* aUri) { bool isFont; return NS_SUCCEEDED(aUri->SchemeIs(FONTTABLEURI_SCHEME, &isFont)) && isFont; } extern nsresult NS_GetBlobForBlobURI(nsIURI* aURI, mozilla::dom::BlobImpl** aBlob);
--- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -136,17 +136,16 @@ static const uint32_t kMaxICCDuration = #define NS_CC_PURPLE_LIMIT 200 // Large value used to specify that a script should run essentially forever #define NS_UNLIMITED_SCRIPT_RUNTIME (0x40000000LL << 32) // if you add statics here, add them to the list in StartupJSEnvironment static nsITimer *sGCTimer; -static nsITimer *sShrinkGCBuffersTimer; static nsITimer *sShrinkingGCTimer; static nsITimer *sCCTimer; static nsITimer *sICCTimer; static nsITimer *sFullGCTimer; static nsITimer *sInterSliceGCTimer; static TimeStamp sLastCCEndTime; @@ -290,17 +289,16 @@ GetCollectionTimeDelta() return 0; } static void KillTimers() { nsJSContext::KillGCTimer(); nsJSContext::KillShrinkingGCTimer(); - nsJSContext::KillShrinkGCBuffersTimer(); nsJSContext::KillCCTimer(); nsJSContext::KillICCTimer(); nsJSContext::KillFullGCTimer(); nsJSContext::KillInterSliceGCTimer(); } // If we collected a substantial amount of cycles, poke the GC since more objects // might be unreachable now. @@ -1183,17 +1181,16 @@ nsJSContext::GarbageCollectNow(JS::gcrea int64_t aSliceMillis) { PROFILER_LABEL("nsJSContext", "GarbageCollectNow", js::ProfileEntry::Category::GC); MOZ_ASSERT_IF(aSliceMillis, aIncremental == IncrementalGC); KillGCTimer(); - KillShrinkGCBuffersTimer(); // Reset sPendingLoadCount in case the timer that fired was a // timer we scheduled due to a normal GC timer firing while // documents were loading. If this happens we're waiting for a // document that is taking a long time to load, and we effectively // ignore the fact that the currently loading documents are still // loading and move on as if they weren't. sPendingLoadCount = 0; @@ -1221,28 +1218,16 @@ nsJSContext::GarbageCollectNow(JS::gcrea if (aIncremental == IncrementalGC) { JS::StartIncrementalGC(sContext, gckind, aReason, aSliceMillis); } else { JS::GCForReason(sContext, gckind, aReason); } } -//static -void -nsJSContext::ShrinkGCBuffersNow() -{ - PROFILER_LABEL("nsJSContext", "ShrinkGCBuffersNow", - js::ProfileEntry::Category::GC); - - KillShrinkGCBuffersTimer(); - - JS::ShrinkGCBuffers(sContext); -} - static void FinishAnyIncrementalGC() { PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GC); if (sCCLockedOut) { // We're in the middle of an incremental GC, so finish it. JS::PrepareForIncrementalGC(sContext); @@ -1726,23 +1711,16 @@ void GCTimerFired(nsITimer *aTimer, void *aClosure) { nsJSContext::KillGCTimer(); uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure); nsJSContext::GarbageCollectNow(static_cast<JS::gcreason::Reason>(reason), nsJSContext::IncrementalGC); } -void -ShrinkGCBuffersTimerFired(nsITimer *aTimer, void *aClosure) -{ - nsJSContext::KillShrinkGCBuffersTimer(); - nsJSContext::ShrinkGCBuffersNow(); -} - // static void ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure) { nsJSContext::KillShrinkingGCTimer(); sIsCompactingOnUserInactive = true; nsJSContext::GarbageCollectNow(JS::gcreason::USER_INACTIVE, nsJSContext::IncrementalGC, @@ -1961,38 +1939,16 @@ nsJSContext::PokeGC(JS::gcreason::Reason : NS_GC_DELAY), nsITimer::TYPE_ONE_SHOT, "GCTimerFired"); first = false; } // static void -nsJSContext::PokeShrinkGCBuffers() -{ - if (sShrinkGCBuffersTimer || sShuttingDown) { - return; - } - - CallCreateInstance("@mozilla.org/timer;1", &sShrinkGCBuffersTimer); - - if (!sShrinkGCBuffersTimer) { - // Failed to create timer (probably because we're in XPCOM shutdown) - return; - } - - sShrinkGCBuffersTimer->InitWithNamedFuncCallback(ShrinkGCBuffersTimerFired, - nullptr, - NS_SHRINK_GC_BUFFERS_DELAY, - nsITimer::TYPE_ONE_SHOT, - "ShrinkGCBuffersTimerFired"); -} - -// static -void nsJSContext::PokeShrinkingGC() { if (sShrinkingGCTimer || sShuttingDown) { return; } CallCreateInstance("@mozilla.org/timer;1", &sShrinkingGCTimer); @@ -2056,26 +2012,16 @@ nsJSContext::KillInterSliceGCTimer() if (sInterSliceGCTimer) { sInterSliceGCTimer->Cancel(); NS_RELEASE(sInterSliceGCTimer); } } //static void -nsJSContext::KillShrinkGCBuffersTimer() -{ - if (sShrinkGCBuffersTimer) { - sShrinkGCBuffersTimer->Cancel(); - NS_RELEASE(sShrinkGCBuffersTimer); - } -} - -//static -void nsJSContext::KillShrinkingGCTimer() { if (sShrinkingGCTimer) { sShrinkingGCTimer->Cancel(); NS_RELEASE(sShrinkingGCTimer); } } @@ -2133,19 +2079,16 @@ static void DOMGCSliceCallback(JSRuntime *aRt, JS::GCProgress aProgress, const JS::GCDescription &aDesc) { NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread"); switch (aProgress) { case JS::GC_CYCLE_BEGIN: { // Prevent cycle collections and shrinking during incremental GC. sCCLockedOut = true; - - nsJSContext::KillShrinkGCBuffersTimer(); - break; } case JS::GC_CYCLE_END: { PRTime delta = GetCollectionTimeDelta(); if (sPostGCEventsToConsole) { NS_NAMED_LITERAL_STRING(kFmt, "GC(T+%.1f)[%s] "); @@ -2190,20 +2133,16 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::G NS_FULL_GC_DELAY, nsITimer::TYPE_ONE_SHOT, "FullGCTimerFired"); } } else { nsJSContext::KillFullGCTimer(); } - if (aDesc.invocationKind_ == GC_NORMAL) { - nsJSContext::PokeShrinkGCBuffers(); - } - if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) { nsCycleCollector_dispatchDeferredDeletion(); } break; } case JS::GC_SLICE_BEGIN:
--- a/dom/base/nsJSEnvironment.h +++ b/dom/base/nsJSEnvironment.h @@ -82,17 +82,16 @@ public: // Setup all the statics etc - safe to call multiple times after Startup(). void EnsureStatics(); static void GarbageCollectNow(JS::gcreason::Reason reason, IsIncremental aIncremental = NonIncrementalGC, IsShrinking aShrinking = NonShrinkingGC, int64_t aSliceMillis = 0); - static void ShrinkGCBuffersNow(); // If aExtraForgetSkippableCalls is -1, forgetSkippable won't be // called even if the previous collection was GC. static void CycleCollectNow(nsICycleCollectorListener *aListener = nullptr, int32_t aExtraForgetSkippableCalls = 0); // Run a cycle collector slice, using a heuristic to decide how long to run it. static void RunCycleCollectorSlice(); @@ -107,19 +106,16 @@ public: static uint32_t GetMaxCCSliceTimeSinceClear(); static void ClearMaxCCSliceTime(); static void RunNextCollectorTimer(); static void PokeGC(JS::gcreason::Reason aReason, int aDelay = 0); static void KillGCTimer(); - static void PokeShrinkGCBuffers(); - static void KillShrinkGCBuffersTimer(); - static void PokeShrinkingGC(); static void KillShrinkingGCTimer(); static void MaybePokeCC(); static void KillCCTimer(); static void KillICCTimer(); static void KillFullGCTimer(); static void KillInterSliceGCTimer();
--- a/dom/bindings/Exceptions.cpp +++ b/dom/bindings/Exceptions.cpp @@ -198,17 +198,26 @@ GetCurrentJSStack(int32_t aMaxDepth) { // is there a current context available? JSContext* cx = nsContentUtils::GetCurrentJSContextForThread(); if (!cx || !js::GetContextCompartment(cx)) { return nullptr; } - return dom::exceptions::CreateStack(cx, aMaxDepth); + static const unsigned MAX_FRAMES = 100; + if (aMaxDepth < 0) { + aMaxDepth = MAX_FRAMES; + } + + JS::StackCapture captureMode = aMaxDepth == 0 + ? JS::StackCapture(JS::AllFrames()) + : JS::StackCapture(JS::MaxFrames(aMaxDepth)); + + return dom::exceptions::CreateStack(cx, mozilla::Move(captureMode)); } namespace exceptions { class JSStackFrame : public nsIStackFrame { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -660,25 +669,20 @@ NS_IMETHODIMP JSStackFrame::ToString(JSC _retval.AppendPrintf(format, NS_ConvertUTF16toUTF8(filename).get(), NS_ConvertUTF16toUTF8(funname).get(), lineno); return NS_OK; } already_AddRefed<nsIStackFrame> -CreateStack(JSContext* aCx, int32_t aMaxDepth) +CreateStack(JSContext* aCx, JS::StackCapture&& aCaptureMode) { - static const unsigned MAX_FRAMES = 100; - if (aMaxDepth < 0) { - aMaxDepth = MAX_FRAMES; - } - JS::Rooted<JSObject*> stack(aCx); - if (!JS::CaptureCurrentStack(aCx, &stack, aMaxDepth)) { + if (!JS::CaptureCurrentStack(aCx, &stack, mozilla::Move(aCaptureMode))) { return nullptr; } if (!stack) { return nullptr; } nsCOMPtr<nsIStackFrame> frame = new JSStackFrame(stack);
--- a/dom/bindings/Exceptions.h +++ b/dom/bindings/Exceptions.h @@ -8,16 +8,17 @@ #define mozilla_dom_Exceptions_h__ // DOM exception throwing machinery (for both main thread and workers). #include <stdint.h> #include "jspubtd.h" #include "nsIException.h" #include "nsStringGlue.h" +#include "jsapi.h" class nsIStackFrame; class nsPIDOMWindowInner; template <class T> struct already_AddRefed; namespace mozilla { namespace dom { @@ -52,19 +53,16 @@ CreateException(JSContext* aCx, nsresult // value is -1, a default maximal depth will be selected. Will return null if // there is no JS stack right now. already_AddRefed<nsIStackFrame> GetCurrentJSStack(int32_t aMaxDepth = -1); // Internal stuff not intended to be widely used. namespace exceptions { -// aMaxDepth can be used to define a maximal depth for the stack trace. If the -// value is -1, a default maximal depth will be selected. Will return null if -// there is no JS stack right now. already_AddRefed<nsIStackFrame> -CreateStack(JSContext* aCx, int32_t aMaxDepth = -1); +CreateStack(JSContext* aCx, JS::StackCapture&& aCaptureMode); } // namespace exceptions } // namespace dom } // namespace mozilla #endif
--- a/dom/canvas/WebGLContextBuffers.cpp +++ b/dom/canvas/WebGLContextBuffers.cpp @@ -317,17 +317,17 @@ WebGLContext::BufferSubDataT(GLenum targ gl->fBufferSubData(target, byteOffset, data.LengthAllowShared(), data.DataAllowShared()); } void WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset, const dom::Nullable<dom::ArrayBuffer>& maybeData) { if (maybeData.IsNull()) { - // see http://www.khronos.org/bugzilla/show_bug.cgi?id=386 + ErrorInvalidValue("BufferSubData: returnedData is null."); return; } BufferSubDataT(target, byteOffset, maybeData.Value()); } void WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset, const dom::SharedArrayBuffer& data)
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini +++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini @@ -4624,17 +4624,16 @@ skip-if = (os == 'android' || os == 'lin [generated/test_2_conformance__attribs__gl-vertexattribpointer-offsets.html] skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2')) [generated/test_2_conformance__attribs__gl-vertexattribpointer.html] fail-if = (os == 'mac') || (os == 'win') skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2')) [generated/test_2_conformance__buffers__buffer-bind-test.html] skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2')) [generated/test_2_conformance__buffers__buffer-data-and-buffer-sub-data.html] -fail-if = (os == 'mac') || (os == 'win') skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2')) [generated/test_2_conformance__buffers__buffer-data-array-buffer-delete.html] skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2')) [generated/test_2_conformance__buffers__element-array-buffer-delete-recreate.html] skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2')) [generated/test_2_conformance__buffers__index-validation-copies-indices.html] skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2')) [generated/test_2_conformance__buffers__index-validation-crash-with-buffer-sub-data.html] @@ -5944,17 +5943,16 @@ fail-if = (os == 'android') fail-if = (os == 'linux') [generated/test_conformance__attribs__gl-vertex-attrib-zero-issues.html] [generated/test_conformance__attribs__gl-vertex-attrib.html] [generated/test_conformance__attribs__gl-vertexattribpointer-offsets.html] [generated/test_conformance__attribs__gl-vertexattribpointer.html] fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux') [generated/test_conformance__buffers__buffer-bind-test.html] [generated/test_conformance__buffers__buffer-data-and-buffer-sub-data.html] -fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux') [generated/test_conformance__buffers__buffer-data-array-buffer-delete.html] [generated/test_conformance__buffers__element-array-buffer-delete-recreate.html] [generated/test_conformance__buffers__index-validation-copies-indices.html] [generated/test_conformance__buffers__index-validation-crash-with-buffer-sub-data.html] [generated/test_conformance__buffers__index-validation-large-buffer.html] [generated/test_conformance__buffers__index-validation-verifies-too-many-indices.html] [generated/test_conformance__buffers__index-validation-with-resized-buffer.html] [generated/test_conformance__buffers__index-validation.html]
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini +++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini @@ -148,34 +148,30 @@ fail-if = (os == 'mac') || (os == 'win') [generated/test_2_conformance__textures__misc__tex-image-with-format-and-type.html] fail-if = (os == 'mac') [generated/test_2_conformance__attribs__gl-vertexattribpointer.html] fail-if = (os == 'mac') || (os == 'win') [generated/test_2_conformance2__buffers__buffer-copying-restrictions.html] fail-if = (os == 'mac') || (os == 'win') [generated/test_2_conformance2__glsl3__forbidden-operators.html] fail-if = (os == 'mac') || (os == 'win') -[generated/test_2_conformance__buffers__buffer-data-and-buffer-sub-data.html] -fail-if = (os == 'mac') || (os == 'win') [generated/test_conformance__textures__misc__tex-sub-image-2d-bad-args.html] fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux') [generated/test_2_conformance2__vertex_arrays__vertex-array-object.html] fail-if = (os == 'mac') || (os == 'win') [generated/test_2_conformance__rendering__negative-one-index.html] fail-if = (os == 'mac') || (os == 'win') [generated/test_conformance__extensions__oes-texture-half-float.html] fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux') [generated/test_2_conformance2__buffers__buffer-copying-contents.html] fail-if = (os == 'mac') || (os == 'win') [generated/test_2_conformance2__reading__read-pixels-pack-parameters.html] fail-if = (os == 'mac') || (os == 'win') [generated/test_conformance__attribs__gl-vertexattribpointer.html] fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux') -[generated/test_conformance__buffers__buffer-data-and-buffer-sub-data.html] -fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux') [generated/test_2_conformance__textures__misc__tex-sub-image-2d-bad-args.html] fail-if = (os == 'mac') || (os == 'win') [generated/test_2_conformance2__misc__expando-loss-2.html] fail-if = (os == 'mac') || (os == 'win') [generated/test_conformance__ogles__GL__biuDepthRange__biuDepthRange_001_to_002.html] fail-if = (os == 'android') || (os == 'linux') [generated/test_conformance__ogles__GL__gl_FragCoord__gl_FragCoord_001_to_003.html] fail-if = (os == 'android') || (os == 'linux')
--- a/dom/console/Console.cpp +++ b/dom/console/Console.cpp @@ -1246,19 +1246,20 @@ Console::MethodInternal(JSContext* aCx, nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); if (NS_WARN_IF(!principal)) { return; } callData->SetOriginAttributes(BasePrincipal::Cast(principal)->OriginAttributesRef()); } - uint32_t maxDepth = ShouldIncludeStackTrace(aMethodName) ? - DEFAULT_MAX_STACKTRACE_DEPTH : 1; - nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, maxDepth); + JS::StackCapture captureMode = ShouldIncludeStackTrace(aMethodName) ? + JS::StackCapture(JS::MaxFrames(DEFAULT_MAX_STACKTRACE_DEPTH)) : + JS::StackCapture(JS::FirstSubsumedFrame(aCx)); + nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, mozilla::Move(captureMode)); if (stack) { callData->mTopStackFrame.emplace(); nsresult rv = StackFrameToStackEntry(aCx, stack, *callData->mTopStackFrame); if (NS_FAILED(rv)) { return; }
--- a/dom/events/EventStates.h +++ b/dom/events/EventStates.h @@ -7,27 +7,30 @@ #ifndef mozilla_EventStates_h_ #define mozilla_EventStates_h_ #include "mozilla/Attributes.h" #include "nsDebug.h" namespace mozilla { +#define NS_EVENT_STATE_HIGHEST_SERVO_BIT 6 + /** * EventStates is the class used to represent the event states of nsIContent * instances. These states are calculated by IntrinsicState() and * ContentStatesChanged() has to be called when one of them changes thus * informing the layout/style engine of the change. * Event states are associated with pseudo-classes. */ class EventStates { public: typedef uint64_t InternalType; + typedef uint8_t ServoType; constexpr EventStates() : mStates(0) { } // NOTE: the ideal scenario would be to have the default constructor public // setting mStates to 0 and this constructor (without = 0) private. @@ -150,16 +153,24 @@ public: } // We only need that method for inDOMUtils::GetContentState. // If inDOMUtils::GetContentState is removed, this method should be removed. InternalType GetInternalValue() const { return mStates; } + /** + * Method used to get the appropriate state representation for Servo. + */ + ServoType ServoValue() const + { + return mStates & ((1 << (NS_EVENT_STATE_HIGHEST_SERVO_BIT + 1)) - 1); + } + private: InternalType mStates; }; } // namespace mozilla /** * The following macros are creating EventStates instance with different @@ -195,20 +206,21 @@ private: #define NS_EVENT_STATE_ENABLED NS_DEFINE_EVENT_STATE_MACRO(3) // Content is disabled. #define NS_EVENT_STATE_DISABLED NS_DEFINE_EVENT_STATE_MACRO(4) // Content is checked. #define NS_EVENT_STATE_CHECKED NS_DEFINE_EVENT_STATE_MACRO(5) // Content is in the indeterminate state. #define NS_EVENT_STATE_INDETERMINATE NS_DEFINE_EVENT_STATE_MACRO(6) -#define NS_EVENT_STATE_HIGHEST_SERVO_BIT 6 - /* * Bits below here do not have Servo-related ordering constraints. + * + * Remember to change NS_EVENT_STATE_HIGHEST_SERVO_BIT at the top of the file if + * this changes! */ // Drag is hovering over content. #define NS_EVENT_STATE_DRAGOVER NS_DEFINE_EVENT_STATE_MACRO(7) // Content is URL's target (ref). #define NS_EVENT_STATE_URLTARGET NS_DEFINE_EVENT_STATE_MACRO(8) // Content is required. #define NS_EVENT_STATE_REQUIRED NS_DEFINE_EVENT_STATE_MACRO(9)
--- a/dom/gamepad/android/AndroidGamepad.cpp +++ b/dom/gamepad/android/AndroidGamepad.cpp @@ -1,23 +1,23 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "AndroidBridge.h" +#include "GeneratedJNIWrappers.h" namespace mozilla { namespace dom { void StartGamepadMonitoring() { - widget::GeckoAppShell::StartMonitoringGamepad(); + java::GeckoAppShell::StartMonitoringGamepad(); } void StopGamepadMonitoring() { - widget::GeckoAppShell::StopMonitoringGamepad(); + java::GeckoAppShell::StopMonitoringGamepad(); } } // namespace dom } // namespace mozilla
--- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -1495,19 +1495,25 @@ nsresult HTMLMediaElement::LoadResource( if (docShell && !docShell->GetAllowMedia()) { return NS_ERROR_FAILURE; } // Set the media element's CORS mode only when loading a resource mCORSMode = AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin)); #ifdef MOZ_EME + bool isBlob = false; if (mMediaKeys && - !IsMediaSourceURI(mLoadingSrc) && - Preferences::GetBool("media.eme.mse-only", true)) { + Preferences::GetBool("media.eme.mse-only", true) && + // We only want mediaSource URLs, but they are BlobURL, so we have to + // check the schema and if they are not MediaStream or real Blob. + (NS_FAILED(mLoadingSrc->SchemeIs(BLOBURI_SCHEME, &isBlob)) || + !isBlob || + IsMediaStreamURI(mLoadingSrc) || + IsBlobURI(mLoadingSrc))) { return NS_ERROR_DOM_NOT_SUPPORTED_ERR; } #endif HTMLMediaElement* other = LookupMediaElementURITable(mLoadingSrc); if (other && other->mDecoder) { // Clone it. nsresult rv = InitializeDecoderAsClone(other->mDecoder);
--- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -2639,18 +2639,19 @@ bool ContentChild::RecvInitBlobURLs(nsTArray<BlobURLRegistrationData>&& aRegistrations) { for (uint32_t i = 0; i < aRegistrations.Length(); ++i) { BlobURLRegistrationData& registration = aRegistrations[i]; RefPtr<BlobImpl> blobImpl = static_cast<BlobChild*>(registration.blobChild())->GetBlobImpl(); MOZ_ASSERT(blobImpl); - nsHostObjectProtocolHandler::AddDataEntry(registration.url(), blobImpl, - registration.principal()); + nsHostObjectProtocolHandler::AddDataEntry(registration.url(), + registration.principal(), + blobImpl); } return true; } bool ContentChild::RecvLastPrivateDocShellDestroyed() { @@ -3391,17 +3392,17 @@ ContentChild::RecvNotifyPushSubscription bool ContentChild::RecvBlobURLRegistration(const nsCString& aURI, PBlobChild* aBlobChild, const IPC::Principal& aPrincipal) { RefPtr<BlobImpl> blobImpl = static_cast<BlobChild*>(aBlobChild)->GetBlobImpl(); MOZ_ASSERT(blobImpl); - nsHostObjectProtocolHandler::AddDataEntry(aURI, blobImpl, aPrincipal); + nsHostObjectProtocolHandler::AddDataEntry(aURI, aPrincipal, blobImpl); return true; } bool ContentChild::RecvBlobURLUnregistration(const nsCString& aURI) { nsHostObjectProtocolHandler::RemoveDataEntry(aURI); return true;
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -2833,17 +2833,17 @@ ContentParent::RecvGetIconForExtension(c bool ContentParent::RecvGetShowPasswordSetting(bool* showPassword) { // default behavior is to show the last password character *showPassword = true; #ifdef MOZ_WIDGET_ANDROID NS_ASSERTION(AndroidBridge::Bridge() != nullptr, "AndroidBridge is not available"); - *showPassword = mozilla::widget::GeckoAppShell::GetShowPasswordSetting(); + *showPassword = java::GeckoAppShell::GetShowPasswordSetting(); #endif return true; } bool ContentParent::RecvFirstIdle() { // When the ContentChild goes idle, it sends us a FirstIdle message @@ -5744,18 +5744,18 @@ ContentParent::RecvStoreAndBroadcastBlob const Principal& aPrincipal) { RefPtr<BlobImpl> blobImpl = static_cast<BlobParent*>(aBlobParent)->GetBlobImpl(); if (NS_WARN_IF(!blobImpl)) { return false; } - if (NS_SUCCEEDED(nsHostObjectProtocolHandler::AddDataEntry(aURI, blobImpl, - aPrincipal))) { + if (NS_SUCCEEDED(nsHostObjectProtocolHandler::AddDataEntry(aURI, aPrincipal, + blobImpl))) { BroadcastBlobURLRegistration(aURI, blobImpl, aPrincipal, this); // We want to store this blobURL, so we can unregister it if the child // crashes. mBlobURLs.AppendElement(aURI); } BroadcastBlobURLRegistration(aURI, blobImpl, aPrincipal, this);
--- a/dom/media/fmp4/MP4Decoder.cpp +++ b/dom/media/fmp4/MP4Decoder.cpp @@ -17,17 +17,16 @@ #include "nsContentTypeParser.h" #include "VideoUtils.h" #ifdef XP_WIN #include "mozilla/WindowsVersion.h" #endif #ifdef MOZ_WIDGET_ANDROID #include "nsIGfxInfo.h" -#include "AndroidBridge.h" #endif #include "mozilla/layers/LayersTypes.h" #include "PDMFactory.h" namespace mozilla { MP4Decoder::MP4Decoder(MediaDecoderOwner* aOwner)
--- a/dom/media/mediasource/MediaSource.cpp +++ b/dom/media/mediasource/MediaSource.cpp @@ -27,20 +27,16 @@ #include "nsPIDOMWindow.h" #include "nsString.h" #include "nsThreadUtils.h" #include "mozilla/Logging.h" #include "nsServiceManagerUtils.h" #include "gfxPlatform.h" #include "mozilla/Snprintf.h" -#ifdef MOZ_WIDGET_ANDROID -#include "AndroidBridge.h" -#endif - struct JSContext; class JSObject; mozilla::LogModule* GetMediaSourceLog() { static mozilla::LazyLogModule sLogModule("MediaSource"); return sLogModule; }
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp +++ b/dom/media/platforms/android/AndroidDecoderModule.cpp @@ -30,17 +30,17 @@ static PRLogModuleInfo* AndroidDecoderMo #undef LOG #define LOG(arg, ...) MOZ_LOG(AndroidDecoderModuleLog(), \ mozilla::LogLevel::Debug, ("AndroidDecoderModule(%p)::%s: " arg, \ this, __func__, ##__VA_ARGS__)) using namespace mozilla; using namespace mozilla::gl; -using namespace mozilla::widget::sdk; +using namespace mozilla::java::sdk; using media::TimeUnit; namespace mozilla { #define INVOKE_CALLBACK(Func, ...) \ if (mCallback) { \ mCallback->Func(__VA_ARGS__); \ } else { \ @@ -169,36 +169,36 @@ class AudioDataDecoder : public MediaCod public: AudioDataDecoder(const AudioInfo& aConfig, MediaFormat::Param aFormat, MediaDataDecoderCallback* aCallback) : MediaCodecDataDecoder(MediaData::Type::AUDIO_DATA, aConfig.mMimeType, aFormat, aCallback) { JNIEnv* const env = jni::GetEnvForThread(); - jni::Object::LocalRef buffer(env); + jni::ByteBuffer::LocalRef buffer(env); NS_ENSURE_SUCCESS_VOID(aFormat->GetByteBuffer(NS_LITERAL_STRING("csd-0"), &buffer)); if (!buffer && aConfig.mCodecSpecificConfig->Length() >= 2) { - buffer = jni::Object::LocalRef::Adopt( - env, env->NewDirectByteBuffer(aConfig.mCodecSpecificConfig->Elements(), - aConfig.mCodecSpecificConfig->Length())); + buffer = jni::ByteBuffer::New( + aConfig.mCodecSpecificConfig->Elements(), + aConfig.mCodecSpecificConfig->Length()); NS_ENSURE_SUCCESS_VOID(aFormat->SetByteBuffer(NS_LITERAL_STRING("csd-0"), buffer)); } } const char* GetDescriptionName() const override { return "android audio decoder"; } nsresult Output(BufferInfo::Param aInfo, void* aBuffer, - MediaFormat::Param aFormat, const TimeUnit& aDuration) + MediaFormat::Param aFormat, const TimeUnit& aDuration) override { // The output on Android is always 16-bit signed nsresult rv; int32_t numChannels; NS_ENSURE_SUCCESS(rv = aFormat->GetInteger(NS_LITERAL_STRING("channel-count"), &numChannels), rv); AudioConfig::ChannelLayout layout(numChannels); if (!layout.IsValid()) { @@ -272,17 +272,17 @@ AndroidDecoderModule::SupportsMimeType(c if ((VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8) && !GetFeatureStatus(nsIGfxInfo::FEATURE_VP8_HW_DECODE)) || (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9) && !GetFeatureStatus(nsIGfxInfo::FEATURE_VP9_HW_DECODE))) { return false; } - return widget::HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType( + return java::HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType( nsCString(TranslateMimeType(aMimeType))); } already_AddRefed<MediaDataDecoder> AndroidDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) { MediaFormat::LocalRef format;
--- a/dom/media/platforms/android/AndroidDecoderModule.h +++ b/dom/media/platforms/android/AndroidDecoderModule.h @@ -38,17 +38,17 @@ public: DecoderNeedsConversion(const TrackInfo& aConfig) const override; }; class MediaCodecDataDecoder : public MediaDataDecoder { public: MediaCodecDataDecoder(MediaData::Type aType, const nsACString& aMimeType, - widget::sdk::MediaFormat::Param aFormat, + java::sdk::MediaFormat::Param aFormat, MediaDataDecoderCallback* aCallback); virtual ~MediaCodecDataDecoder(); RefPtr<MediaDataDecoder::InitPromise> Init() override; nsresult Flush() override; nsresult Drain() override; nsresult Shutdown() override; @@ -68,60 +68,60 @@ protected: kStopping, kShutdown }; friend class AndroidDecoderModule; static const char* ModuleStateStr(ModuleState aState); - virtual nsresult InitDecoder(widget::sdk::Surface::Param aSurface); + virtual nsresult InitDecoder(java::sdk::Surface::Param aSurface); - virtual nsresult Output(widget::sdk::BufferInfo::Param aInfo, void* aBuffer, - widget::sdk::MediaFormat::Param aFormat, const media::TimeUnit& aDuration) + virtual nsresult Output(java::sdk::BufferInfo::Param aInfo, void* aBuffer, + java::sdk::MediaFormat::Param aFormat, const media::TimeUnit& aDuration) { return NS_OK; } - virtual nsresult PostOutput(widget::sdk::BufferInfo::Param aInfo, - widget::sdk::MediaFormat::Param aFormat, const media::TimeUnit& aDuration) + virtual nsresult PostOutput(java::sdk::BufferInfo::Param aInfo, + java::sdk::MediaFormat::Param aFormat, const media::TimeUnit& aDuration) { return NS_OK; } virtual void Cleanup() {}; nsresult ResetInputBuffers(); nsresult ResetOutputBuffers(); nsresult GetInputBuffer(JNIEnv* env, int index, jni::Object::LocalRef* buffer); bool WaitForInput(); already_AddRefed<MediaRawData> PeekNextSample(); nsresult QueueSample(const MediaRawData* aSample); nsresult QueueEOS(); void HandleEOS(int32_t aOutputStatus); Maybe<media::TimeUnit> GetOutputDuration(); - nsresult ProcessOutput(widget::sdk::BufferInfo::Param aInfo, - widget::sdk::MediaFormat::Param aFormat, + nsresult ProcessOutput(java::sdk::BufferInfo::Param aInfo, + java::sdk::MediaFormat::Param aFormat, int32_t aStatus); ModuleState State() const; // Sets decoder state and returns whether the new state has become effective. bool State(ModuleState aState); void DecoderLoop(); virtual void ClearQueue(); MediaData::Type mType; nsAutoCString mMimeType; - widget::sdk::MediaFormat::GlobalRef mFormat; + java::sdk::MediaFormat::GlobalRef mFormat; MediaDataDecoderCallback* mCallback; - widget::sdk::MediaCodec::GlobalRef mDecoder; + java::sdk::MediaCodec::GlobalRef mDecoder; jni::ObjectArray::GlobalRef mInputBuffers; jni::ObjectArray::GlobalRef mOutputBuffers; nsCOMPtr<nsIThread> mThread; // Only these members are protected by mMonitor. Monitor mMonitor;
--- a/dom/media/webaudio/test/mochitest.ini +++ b/dom/media/webaudio/test/mochitest.ini @@ -88,16 +88,17 @@ tags=capturestream [test_bug964376.html] [test_bug966247.html] tags=capturestream [test_bug972678.html] [test_bug1118372.html] [test_bug1027864.html] [test_bug1056032.html] skip-if = toolkit == 'android' # bug 1056706 +[test_bug1255618.html] [test_bug1267579.html] [test_channelMergerNode.html] [test_channelMergerNodeWithVolume.html] [test_channelSplitterNode.html] [test_channelSplitterNodeWithVolume.html] skip-if = (android_version == '18' && debug) # bug 1158417 [test_convolverNode.html] [test_convolverNode_mono_mono.html]
new file mode 100644 --- /dev/null +++ b/dom/media/webaudio/test/test_bug1255618.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test sync XHR does not crash unlinked AudioContext</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> +SimpleTest.waitForExplicitFinish(); + +const filename = "test_bug1255618.html"; + +function collect_and_fetch() { + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + + var xhr = new XMLHttpRequest(); + xhr.open("GET", filename, false); + var ended = false; + xhr.onloadend = function() { ended = true; } + // Sync XHR will suspend timeouts, which involves any AudioContexts still + // registered with the window. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1255618#c0 + xhr.send(null); + + ok(ended, "No crash during fetch"); + SimpleTest.finish(); +} + +var ac = new AudioContext(); + +ac.onstatechange = function () { + ac.onstatechange = null; + is(ac.state, "running", "statechange to running"); + ac = null; + SimpleTest.executeSoon(collect_and_fetch); +} + +</script> +</body>
--- a/dom/media/webrtc/MediaEngineDefault.cpp +++ b/dom/media/webrtc/MediaEngineDefault.cpp @@ -14,17 +14,16 @@ #include "prmem.h" #include "nsContentUtils.h" #include "nsIFilePicker.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #ifdef MOZ_WIDGET_ANDROID -#include "AndroidBridge.h" #include "nsISupportsUtils.h" #endif #ifdef MOZ_WEBRTC #include "YuvStamper.h" #endif #define AUDIO_RATE mozilla::MediaEngine::DEFAULT_SAMPLE_RATE
--- a/dom/mobilemessage/android/SmsManager.h +++ b/dom/mobilemessage/android/SmsManager.h @@ -5,17 +5,17 @@ #ifndef SmsManager_h__ #define SmsManager_h__ #include "GeneratedJNINatives.h" namespace mozilla { -class SmsManager : public widget::GeckoSmsManager::Natives<SmsManager> +class SmsManager : public java::GeckoSmsManager::Natives<SmsManager> { private: SmsManager(); public: static void NotifySmsReceived(int32_t aId, jni::String::Param aSender, jni::String::Param aBody,
--- a/dom/plugins/base/android/ANPEvent.cpp +++ b/dom/plugins/base/android/ANPEvent.cpp @@ -3,17 +3,16 @@ * 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 "assert.h" #include "ANPBase.h" #include <android/log.h> #include "nsThreadUtils.h" #include "nsNPAPIPluginInstance.h" -#include "AndroidBridge.h" #include "nsNPAPIPlugin.h" #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) #define ASSIGN(obj, name) (obj)->name = anp_event_##name void anp_event_postEvent(NPP instance, const ANPEvent* event) {
--- a/dom/plugins/base/android/ANPNativeWindow.cpp +++ b/dom/plugins/base/android/ANPNativeWindow.cpp @@ -1,16 +1,15 @@ /* -*- Mode: IDL; 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/. */ // must include config.h first for webkit to fiddle with new/delete #include <android/log.h> -#include "AndroidBridge.h" #include "ANPBase.h" #include "nsIPluginInstanceOwner.h" #include "nsPluginInstanceOwner.h" #include "nsNPAPIPluginInstance.h" #include "gfxRect.h" using namespace mozilla;
--- a/dom/plugins/base/android/ANPSystem.cpp +++ b/dom/plugins/base/android/ANPSystem.cpp @@ -1,22 +1,23 @@ /* -*- Mode: IDL; 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 "base/basictypes.h" +#include "ANPBase.h" +#include "GeneratedJNIWrappers.h" +#include "PluginPRLibrary.h" #include "assert.h" -#include "ANPBase.h" -#include <android/log.h> #include "nsNPAPIPluginInstance.h" -#include "AndroidBridge.h" #include "nsNPAPIPlugin.h" -#include "PluginPRLibrary.h" + +#include <android/log.h> #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) #define ASSIGN(obj, name) (obj)->name = anp_system_##name const char* anp_system_getApplicationDataDirectory(NPP instance) { static const char *dir = nullptr; @@ -55,17 +56,17 @@ jclass anp_system_loadJavaClass(NPP inst LOG("%s", __PRETTY_FUNCTION__); nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); mozilla::PluginPRLibrary* lib = static_cast<mozilla::PluginPRLibrary*>(pinst->GetPlugin()->GetLibrary()); nsCString libName; lib->GetLibraryPath(libName); - return mozilla::widget::GeckoAppShell::LoadPluginClass(className, libName).Forget(); + return mozilla::java::GeckoAppShell::LoadPluginClass(className, libName).Forget(); } void anp_system_setPowerState(NPP instance, ANPPowerState powerState) { nsNPAPIPluginInstance* pinst = nsNPAPIPluginInstance::GetFromNPP(instance); if (pinst) { pinst->SetWakeLock(powerState == kScreenOn_ANPPowerState);
--- a/dom/plugins/base/android/ANPVideo.cpp +++ b/dom/plugins/base/android/ANPVideo.cpp @@ -1,14 +1,13 @@ /* 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 <android/log.h> -#include "AndroidBridge.h" #include "ANPBase.h" #include "nsIPluginInstanceOwner.h" #include "nsPluginInstanceOwner.h" #include "nsNPAPIPluginInstance.h" #include "gfxRect.h" #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) #define ASSIGN(obj, name) (obj)->name = anp_video_##name
--- a/dom/plugins/base/android/ANPWindow.cpp +++ b/dom/plugins/base/android/ANPWindow.cpp @@ -2,17 +2,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 "base/basictypes.h" #include "assert.h" #include "ANPBase.h" #include <android/log.h> -#include "AndroidBridge.h" #include "nsNPAPIPluginInstance.h" #include "nsPluginInstanceOwner.h" #include "nsWindow.h" #include "mozilla/dom/ScreenOrientation.h" #undef LOG #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) #define ASSIGN(obj, name) (obj)->name = anp_window_##name
--- a/dom/plugins/base/nsNPAPIPlugin.cpp +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -96,17 +96,17 @@ using mozilla::plugins::PluginModuleCont #include "mozilla/a11y/Compatibility.h" #endif #endif #ifdef MOZ_WIDGET_ANDROID #include <android/log.h> #include "android_npapi.h" #include "ANPBase.h" -#include "AndroidBridge.h" +#include "GeneratedJNIWrappers.h" #undef LOG #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) #endif #include "nsIAudioChannelAgent.h" #include "AudioChannelService.h" using namespace mozilla; @@ -2110,17 +2110,17 @@ NPError case kSupportedDrawingModel_ANPGetValue: { LOG("get supported drawing model"); return NPERR_GENERIC_ERROR; } case kJavaContext_ANPGetValue: { LOG("get java context"); - auto ret = widget::GeckoAppShell::GetContext(); + auto ret = java::GeckoAppShell::GetContext(); if (!ret) return NPERR_GENERIC_ERROR; *static_cast<jobject*>(result) = ret.Forget(); return NPERR_NO_ERROR; } case kAudioTrackInterfaceV1_ANPGetValue: {
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp +++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp @@ -783,17 +783,17 @@ void nsNPAPIPluginInstance::NotifyFullSc if (RUNNING != mRunning || mFullScreen == aFullScreen) return; mFullScreen = aFullScreen; SendLifecycleEvent(this, mFullScreen ? kEnterFullScreen_ANPLifecycleAction : kExitFullScreen_ANPLifecycleAction); if (mFullScreen && mFullScreenOrientation != dom::eScreenOrientation_None) { - widget::GeckoAppShell::LockScreenOrientation(mFullScreenOrientation); + java::GeckoAppShell::LockScreenOrientation(mFullScreenOrientation); } } void nsNPAPIPluginInstance::NotifySize(nsIntSize size) { if (kOpenGL_ANPDrawingModel != GetANPDrawingModel() || size == mCurrentSize) return; @@ -840,21 +840,21 @@ void nsNPAPIPluginInstance::SetFullScree uint32_t oldOrientation = mFullScreenOrientation; mFullScreenOrientation = orientation; if (mFullScreen) { // We're already fullscreen so immediately apply the orientation change if (mFullScreenOrientation != dom::eScreenOrientation_None) { - widget::GeckoAppShell::LockScreenOrientation(mFullScreenOrientation); + java::GeckoAppShell::LockScreenOrientation(mFullScreenOrientation); } else if (oldOrientation != dom::eScreenOrientation_None) { // We applied an orientation when we entered fullscreen, but // we don't want it anymore - widget::GeckoAppShell::UnlockScreenOrientation(); + java::GeckoAppShell::UnlockScreenOrientation(); } } } void nsNPAPIPluginInstance::PopPostedEvent(PluginEventRunnable* r) { mPostedEvents.RemoveElement(r); }
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -1554,17 +1554,17 @@ bool nsPluginInstanceOwner::AddPluginVie return true; } void nsPluginInstanceOwner::RemovePluginView() { if (!mInstance || !mJavaView) return; - widget::GeckoAppShell::RemovePluginView( + java::GeckoAppShell::RemovePluginView( jni::Object::Ref::From(jobject(mJavaView)), mFullScreen); jni::GetGeckoThreadEnv()->DeleteGlobalRef((jobject)mJavaView); mJavaView = nullptr; if (mFullScreen) sFullScreenInstance = nullptr; }
--- a/dom/system/android/AndroidLocationProvider.cpp +++ b/dom/system/android/AndroidLocationProvider.cpp @@ -1,18 +1,18 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsGeolocation.h" #include "nsGeoPosition.h" -#include "AndroidBridge.h" #include "AndroidLocationProvider.h" +#include "GeneratedJNIWrappers.h" using namespace mozilla; extern nsIGeolocationUpdate *gLocationCallback; NS_IMPL_ISUPPORTS(AndroidLocationProvider, nsIGeolocationProvider) AndroidLocationProvider::AndroidLocationProvider() @@ -22,34 +22,34 @@ AndroidLocationProvider::AndroidLocation AndroidLocationProvider::~AndroidLocationProvider() { NS_IF_RELEASE(gLocationCallback); } NS_IMETHODIMP AndroidLocationProvider::Startup() { - widget::GeckoAppShell::EnableLocation(true); + java::GeckoAppShell::EnableLocation(true); return NS_OK; } NS_IMETHODIMP AndroidLocationProvider::Watch(nsIGeolocationUpdate* aCallback) { NS_IF_RELEASE(gLocationCallback); gLocationCallback = aCallback; NS_IF_ADDREF(gLocationCallback); return NS_OK; } NS_IMETHODIMP AndroidLocationProvider::Shutdown() { - widget::GeckoAppShell::EnableLocation(false); + java::GeckoAppShell::EnableLocation(false); return NS_OK; } NS_IMETHODIMP AndroidLocationProvider::SetHighAccuracy(bool enable) { - widget::GeckoAppShell::EnableLocationHighAccuracy(enable); + java::GeckoAppShell::EnableLocationHighAccuracy(enable); return NS_OK; }
--- a/dom/system/android/nsHapticFeedback.cpp +++ b/dom/system/android/nsHapticFeedback.cpp @@ -1,20 +1,20 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/ContentChild.h" #include "nsHapticFeedback.h" -#include "AndroidBridge.h" +#include "GeneratedJNIWrappers.h" using namespace mozilla; NS_IMPL_ISUPPORTS(nsHapticFeedback, nsIHapticFeedback) NS_IMETHODIMP nsHapticFeedback::PerformSimpleAction(int32_t aType) { - widget::GeckoAppShell::PerformHapticFeedback(aType == LongPress); + java::GeckoAppShell::PerformHapticFeedback(aType == LongPress); return NS_OK; }
--- a/dom/url/URL.cpp +++ b/dom/url/URL.cpp @@ -29,16 +29,40 @@ namespace mozilla { namespace dom { /////////////////////////////////////////////////////////////////////////////// // URL for main-thread /////////////////////////////////////////////////////////////////////////////// namespace { +template<typename T> +void +CreateObjectURLInternal(const GlobalObject& aGlobal, T aObject, + nsAString& aResult, ErrorResult& aRv) +{ + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + if (NS_WARN_IF(!global)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + nsCOMPtr<nsIPrincipal> principal = + nsContentUtils::ObjectPrincipal(aGlobal.Get()); + + nsAutoCString url; + aRv = nsHostObjectProtocolHandler::AddDataEntry(aObject, principal, url); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + global->RegisterHostObjectURI(url); + CopyASCIItoUTF16(url, aResult); +} + // The URL implementation for the main-thread class URLMainThread final : public URL { public: static already_AddRefed<URLMainThread> Constructor(const GlobalObject& aGlobal, const nsAString& aURL, URL& aBase, ErrorResult& aRv); @@ -55,44 +79,34 @@ public: ErrorResult& aRv); static void CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob, const objectURLOptions& aOptions, nsAString& aResult, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); - CreateObjectURLInternal(aGlobal, aBlob.Impl(), - NS_LITERAL_CSTRING(BLOBURI_SCHEME), aOptions, - aResult, aRv); + CreateObjectURLInternal(aGlobal, aBlob.Impl(), aResult, aRv); } static void CreateObjectURL(const GlobalObject& aGlobal, DOMMediaStream& aStream, const objectURLOptions& aOptions, nsAString& aResult, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); - CreateObjectURLInternal(aGlobal, &aStream, - NS_LITERAL_CSTRING(MEDIASTREAMURI_SCHEME), aOptions, - aResult, aRv); + CreateObjectURLInternal(aGlobal, &aStream, aResult, aRv); } static void CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource, const objectURLOptions& aOptions, nsAString& aResult, ErrorResult& aRv); static void - CreateObjectURLInternal(const GlobalObject& aGlobal, nsISupports* aObject, - const nsACString& aScheme, - const objectURLOptions& aOptions, - nsAString& aResult, ErrorResult& aRv); - - static void RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL, ErrorResult& aRv); URLMainThread(nsISupports* aParent, already_AddRefed<nsIURI> aURI) : URL(aParent) , mURI(aURI) { MOZ_ASSERT(NS_IsMainThread()); @@ -241,62 +255,32 @@ URLMainThread::CreateObjectURL(const Glo nsAString& aResult, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr<nsIPrincipal> principal = nsContentUtils::ObjectPrincipal(aGlobal.Get()); nsAutoCString url; - aRv = nsHostObjectProtocolHandler:: - AddDataEntry(NS_LITERAL_CSTRING(MEDIASOURCEURI_SCHEME), - &aSource, principal, url); + aRv = nsHostObjectProtocolHandler::AddDataEntry(&aSource, principal, url); if (NS_WARN_IF(aRv.Failed())) { return; } nsCOMPtr<nsIRunnable> revocation = NS_NewRunnableFunction( [url] { nsHostObjectProtocolHandler::RemoveDataEntry(url); }); nsContentUtils::RunInStableState(revocation.forget()); CopyASCIItoUTF16(url, aResult); } /* static */ void -URLMainThread::CreateObjectURLInternal(const GlobalObject& aGlobal, - nsISupports* aObject, - const nsACString& aScheme, - const objectURLOptions& aOptions, - nsAString& aResult, ErrorResult& aRv) -{ - nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); - if (!global) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } - - nsCOMPtr<nsIPrincipal> principal = - nsContentUtils::ObjectPrincipal(aGlobal.Get()); - - nsAutoCString url; - nsresult rv = nsHostObjectProtocolHandler::AddDataEntry(aScheme, aObject, - principal, url); - if (NS_FAILED(rv)) { - aRv.Throw(rv); - return; - } - - global->RegisterHostObjectURI(url); - CopyASCIItoUTF16(url, aResult); -} - -/* static */ void URLMainThread::RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); if (!global) { aRv.Throw(NS_ERROR_FAILURE); return; @@ -821,19 +805,18 @@ public: DebugOnly<bool> isMutable; MOZ_ASSERT(NS_SUCCEEDED(mBlobImpl->GetMutable(&isMutable))); MOZ_ASSERT(!isMutable); nsCOMPtr<nsIPrincipal> principal = mWorkerPrivate->GetPrincipal(); nsAutoCString url; - nsresult rv = nsHostObjectProtocolHandler::AddDataEntry( - NS_LITERAL_CSTRING(BLOBURI_SCHEME), - mBlobImpl, principal, url); + nsresult rv = + nsHostObjectProtocolHandler::AddDataEntry(mBlobImpl, principal, url); if (NS_FAILED(rv)) { NS_WARNING("Failed to add data entry for the blob!"); SetDOMStringToNull(mURL); return false; } if (!mWorkerPrivate->IsSharedWorker() &&
--- a/dom/workers/WorkerRunnable.cpp +++ b/dom/workers/WorkerRunnable.cpp @@ -255,16 +255,20 @@ WorkerRunnable::Run() Cancel(); MOZ_ASSERT(mCallingCancelWithinRun); mCallingCancelWithinRun = false; MOZ_ASSERT(IsCanceled(), "Subclass Cancel() didn't set IsCanceled()!"); + if (mBehavior == WorkerThreadModifyBusyCount) { + mWorkerPrivate->ModifyBusyCountFromWorker(false); + } + return NS_OK; } bool result = PreRun(mWorkerPrivate); if (!result) { MOZ_ASSERT(targetIsWorkerThread, "The only PreRun implementation that can fail is " "ScriptExecutorRunnable");
--- a/extensions/spellcheck/hunspell/src/moz.build +++ b/extensions/spellcheck/hunspell/src/moz.build @@ -27,8 +27,12 @@ LOCAL_INCLUDES += [ '../glue', ] # We allow warnings for third-party code that can be updated from upstream. ALLOW_COMPILER_WARNINGS = True include('/ipc/chromium/chromium-config.mozbuild') +if CONFIG['CLANG_CXX'] or CONFIG['CLANG_CL']: + CXXFLAGS += [ + '-Wno-implicit-fallthrough', + ]
--- a/gfx/gl/AndroidSurfaceTexture.cpp +++ b/gfx/gl/AndroidSurfaceTexture.cpp @@ -15,18 +15,18 @@ #include "nsThreadUtils.h" #include "mozilla/gfx/Matrix.h" #include "GeneratedJNIWrappers.h" #include "SurfaceTexture.h" #include "GLContext.h" using namespace mozilla; using namespace mozilla::jni; -using namespace mozilla::widget; -using namespace mozilla::widget::sdk; +using namespace mozilla::java; +using namespace mozilla::java::sdk; namespace mozilla { namespace gl { // Maintains a mapping between AndroidSurfaceTexture instances and their // unique numerical IDs. [thread-safe] class InstanceMap {
--- a/gfx/gl/AndroidSurfaceTexture.h +++ b/gfx/gl/AndroidSurfaceTexture.h @@ -78,27 +78,27 @@ public: // if the upstream callback is received on a different thread void SetFrameAvailableCallback(nsIRunnable* aRunnable); // Only should be called by AndroidJNI when we get a // callback from the underlying SurfaceTexture instance void NotifyFrameAvailable(); GLuint Texture() const { return mTexture; } - const widget::sdk::Surface::Ref& JavaSurface() const { return mSurface; } + const java::sdk::Surface::Ref& JavaSurface() const { return mSurface; } private: AndroidSurfaceTexture(); ~AndroidSurfaceTexture(); bool Init(GLContext* aContext, GLuint aTexture); GLuint mTexture; - widget::sdk::SurfaceTexture::GlobalRef mSurfaceTexture; - widget::sdk::Surface::GlobalRef mSurface; + java::sdk::SurfaceTexture::GlobalRef mSurfaceTexture; + java::sdk::Surface::GlobalRef mSurface; GLContext* mAttachedContext; RefPtr<AndroidNativeWindow> mNativeWindow; int mID; nsCOMPtr<nsIRunnable> mFrameAvailableCallback; mutable Monitor mMonitor;
--- a/gfx/ipc/CompositorSession.h +++ b/gfx/ipc/CompositorSession.h @@ -18,17 +18,17 @@ class CompositorWidgetDelegate; } // namespace widget namespace gfx { class GPUProcessHost; class GPUProcessManager; } // namespace gfx namespace layers { class GeckoContentController; -class APZCTreeManager; +class IAPZCTreeManager; class CompositorBridgeParent; class CompositorBridgeChild; class ClientLayerManager; // A CompositorSession provides access to a compositor without exposing whether // or not it's in-process or out-of-process. class CompositorSession { @@ -46,17 +46,17 @@ public: // This returns a CompositorBridgeParent if the compositor resides in the same process. virtual CompositorBridgeParent* GetInProcessBridge() const = 0; // Set the GeckoContentController for the root of the layer tree. virtual void SetContentController(GeckoContentController* aController) = 0; // Return the Async Pan/Zoom Tree Manager for this compositor. - virtual already_AddRefed<APZCTreeManager> GetAPZCTreeManager() const = 0; + virtual already_AddRefed<IAPZCTreeManager> GetAPZCTreeManager() const = 0; // Return the child end of the compositor IPC bridge. CompositorBridgeChild* GetCompositorBridgeChild(); // Return the proxy for accessing the compositor's widget. CompositorWidgetDelegate* GetCompositorWidgetDelegate() { return mCompositorWidgetDelegate; }
--- a/gfx/ipc/GPUProcessManager.cpp +++ b/gfx/ipc/GPUProcessManager.cpp @@ -478,17 +478,17 @@ GPUProcessManager::CreateContentVRManage return false; } } *aOutEndpoint = Move(childPipe); return true; } -already_AddRefed<APZCTreeManager> +already_AddRefed<IAPZCTreeManager> GPUProcessManager::GetAPZCTreeManagerForLayers(uint64_t aLayersId) { return CompositorBridgeParent::GetAPZCTreeManager(aLayersId); } uint64_t GPUProcessManager::AllocateLayerTreeId() {
--- a/gfx/ipc/GPUProcessManager.h +++ b/gfx/ipc/GPUProcessManager.h @@ -18,17 +18,17 @@ #include "mozilla/ipc/Transport.h" #include "nsIObserverService.h" #include "nsThreadUtils.h" class nsBaseWidget; namespace mozilla { namespace layers { -class APZCTreeManager; +class IAPZCTreeManager; class CompositorSession; class ClientLayerManager; class CompositorUpdateObserver; class PCompositorBridgeChild; class PImageBridgeChild; } // namespace layers namespace widget { class CompositorWidget; @@ -47,19 +47,19 @@ class VsyncBridgeChild; class VsyncIOThreadHolder; class PVRManagerChild; // The GPUProcessManager is a singleton responsible for creating GPU-bound // objects that may live in another process. Currently, it provides access // to the compositor via CompositorBridgeParent. class GPUProcessManager final : public GPUProcessHost::Listener { - typedef layers::APZCTreeManager APZCTreeManager; typedef layers::ClientLayerManager ClientLayerManager; typedef layers::CompositorSession CompositorSession; + typedef layers::IAPZCTreeManager IAPZCTreeManager; typedef layers::CompositorUpdateObserver CompositorUpdateObserver; typedef layers::PCompositorBridgeChild PCompositorBridgeChild; typedef layers::PImageBridgeChild PImageBridgeChild; public: static void Initialize(); static void Shutdown(); static GPUProcessManager* Get(); @@ -86,17 +86,17 @@ public: ipc::Endpoint<PCompositorBridgeChild>* aOutEndpoint); bool CreateContentImageBridge(base::ProcessId aOtherProcess, ipc::Endpoint<PImageBridgeChild>* aOutEndpoint); bool CreateContentVRManager(base::ProcessId aOtherProcess, ipc::Endpoint<PVRManagerChild>* aOutEndpoint); // This returns a reference to the APZCTreeManager to which // pan/zoom-related events can be sent. - already_AddRefed<APZCTreeManager> GetAPZCTreeManagerForLayers(uint64_t aLayersId); + already_AddRefed<IAPZCTreeManager> GetAPZCTreeManagerForLayers(uint64_t aLayersId); // Allocate an ID that can be used to refer to a layer tree and // associated resources that live only on the compositor thread. // // Must run on the content main thread. uint64_t AllocateLayerTreeId(); // Release compositor-thread resources referred to by |aID|.
--- a/gfx/ipc/InProcessCompositorSession.cpp +++ b/gfx/ipc/InProcessCompositorSession.cpp @@ -1,16 +1,20 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=99: */ /* 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 "InProcessCompositorSession.h" +// so we can cast an APZCTreeManager to an IAPZCTreeManager +#include "mozilla/layers/APZCTreeManager.h" +#include "mozilla/layers/IAPZCTreeManager.h" + namespace mozilla { namespace layers { InProcessCompositorSession::InProcessCompositorSession(widget::CompositorWidget* aWidget, CompositorBridgeChild* aChild, CompositorBridgeParent* aParent) : CompositorSession(aWidget->AsDelegate(), aChild, aParent->RootLayerTreeId()), mCompositorBridgeParent(aParent), @@ -45,17 +49,17 @@ InProcessCompositorSession::GetInProcess } void InProcessCompositorSession::SetContentController(GeckoContentController* aController) { mCompositorBridgeParent->SetControllerForLayerTree(mRootLayerTreeId, aController); } -already_AddRefed<APZCTreeManager> +already_AddRefed<IAPZCTreeManager> InProcessCompositorSession::GetAPZCTreeManager() const { return mCompositorBridgeParent->GetAPZCTreeManager(mRootLayerTreeId); } void InProcessCompositorSession::Shutdown() {
--- a/gfx/ipc/InProcessCompositorSession.h +++ b/gfx/ipc/InProcessCompositorSession.h @@ -24,17 +24,17 @@ public: const uint64_t& aRootLayerTreeId, CSSToLayoutDeviceScale aScale, bool aUseAPZ, bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize); CompositorBridgeParent* GetInProcessBridge() const override; void SetContentController(GeckoContentController* aController) override; - already_AddRefed<APZCTreeManager> GetAPZCTreeManager() const override; + already_AddRefed<IAPZCTreeManager> GetAPZCTreeManager() const override; void Shutdown() override; private: InProcessCompositorSession(widget::CompositorWidget* aWidget, CompositorBridgeChild* aChild, CompositorBridgeParent* aParent); private:
--- a/gfx/ipc/RemoteCompositorSession.cpp +++ b/gfx/ipc/RemoteCompositorSession.cpp @@ -25,17 +25,17 @@ RemoteCompositorSession::GetInProcessBri } void RemoteCompositorSession::SetContentController(GeckoContentController* aController) { MOZ_CRASH("NYI"); } -already_AddRefed<APZCTreeManager> +already_AddRefed<IAPZCTreeManager> RemoteCompositorSession::GetAPZCTreeManager() const { return nullptr; } void RemoteCompositorSession::Shutdown() {
--- a/gfx/ipc/RemoteCompositorSession.h +++ b/gfx/ipc/RemoteCompositorSession.h @@ -17,17 +17,17 @@ class RemoteCompositorSession final : pu { public: RemoteCompositorSession(CompositorBridgeChild* aChild, CompositorWidgetDelegate* aWidgetDelegate, const uint64_t& aRootLayerTreeId); CompositorBridgeParent* GetInProcessBridge() const override; void SetContentController(GeckoContentController* aController) override; - already_AddRefed<APZCTreeManager> GetAPZCTreeManager() const override; + already_AddRefed<IAPZCTreeManager> GetAPZCTreeManager() const override; void Shutdown() override; }; } // namespace layers } // namespace mozilla #endif // include_mozilla_gfx_ipc_RemoteCompositorSession_h
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/public/IAPZCTreeManager.cpp @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=99: */ +/* 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 "mozilla/layers/IAPZCTreeManager.h" + +#include "gfxPrefs.h" // for gfxPrefs +#include "InputData.h" // for InputData, etc +#include "mozilla/EventStateManager.h" // for WheelPrefs +#include "mozilla/layers/APZThreadUtils.h" // for AssertOnCompositorThread, etc +#include "mozilla/MouseEvents.h" // for WidgetMouseEvent +#include "mozilla/TouchEvents.h" // for WidgetTouchEvent + +namespace mozilla { +namespace layers { + +static bool +WillHandleMouseEvent(const WidgetMouseEventBase& aEvent) +{ + return aEvent.mMessage == eMouseMove || + aEvent.mMessage == eMouseDown || + aEvent.mMessage == eMouseUp || + aEvent.mMessage == eDragEnd; +} + +// Returns whether or not a wheel event action will be (or was) performed by +// APZ. If this returns true, the event must not perform a synchronous +// scroll. +// +// Even if this returns false, all wheel events in APZ-aware widgets must +// be sent through APZ so they are transformed correctly for TabParent. +static bool +WillHandleWheelEvent(WidgetWheelEvent* aEvent) +{ + return EventStateManager::WheelEventIsScrollAction(aEvent) && + (aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE || + aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL || + aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PAGE); +} + +nsEventStatus +IAPZCTreeManager::ReceiveInputEvent( + WidgetInputEvent& aEvent, + ScrollableLayerGuid* aOutTargetGuid, + uint64_t* aOutInputBlockId) +{ + APZThreadUtils::AssertOnControllerThread(); + + // Initialize aOutInputBlockId to a sane value, and then later we overwrite + // it if the input event goes into a block. + if (aOutInputBlockId) { + *aOutInputBlockId = 0; + } + + switch (aEvent.mClass) { + case eMouseEventClass: + case eDragEventClass: { + + WidgetMouseEvent& mouseEvent = *aEvent.AsMouseEvent(); + + // Note, we call this before having transformed the reference point. + if (mouseEvent.IsReal()) { + UpdateWheelTransaction(mouseEvent.mRefPoint, mouseEvent.mMessage); + } + + if (WillHandleMouseEvent(mouseEvent)) { + + MouseInput input(mouseEvent); + input.mOrigin = ScreenPoint(mouseEvent.mRefPoint.x, mouseEvent.mRefPoint.y); + + nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId); + + mouseEvent.mRefPoint.x = input.mOrigin.x; + mouseEvent.mRefPoint.y = input.mOrigin.y; + mouseEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ; + return status; + + } + + TransformEventRefPoint(&mouseEvent.mRefPoint, aOutTargetGuid); + return nsEventStatus_eIgnore; + } + case eTouchEventClass: { + + WidgetTouchEvent& touchEvent = *aEvent.AsTouchEvent(); + MultiTouchInput touchInput(touchEvent); + nsEventStatus result = ReceiveInputEvent(touchInput, aOutTargetGuid, aOutInputBlockId); + // touchInput was modified in-place to possibly remove some + // touch points (if we are overscrolled), and the coordinates were + // modified using the APZ untransform. We need to copy these changes + // back into the WidgetInputEvent. + touchEvent.mTouches.Clear(); + touchEvent.mTouches.SetCapacity(touchInput.mTouches.Length()); + for (size_t i = 0; i < touchInput.mTouches.Length(); i++) { + *touchEvent.mTouches.AppendElement() = + touchInput.mTouches[i].ToNewDOMTouch(); + } + touchEvent.mFlags.mHandledByAPZ = touchInput.mHandledByAPZ; + return result; + + } + case eWheelEventClass: { + WidgetWheelEvent& wheelEvent = *aEvent.AsWheelEvent(); + + if (WillHandleWheelEvent(&wheelEvent)) { + + ScrollWheelInput::ScrollMode scrollMode = ScrollWheelInput::SCROLLMODE_INSTANT; + if (gfxPrefs::SmoothScrollEnabled() && + ((wheelEvent.mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE && + gfxPrefs::WheelSmoothScrollEnabled()) || + (wheelEvent.mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PAGE && + gfxPrefs::PageSmoothScrollEnabled()))) + { + scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH; + } + + ScreenPoint origin(wheelEvent.mRefPoint.x, wheelEvent.mRefPoint.y); + ScrollWheelInput input(wheelEvent.mTime, wheelEvent.mTimeStamp, 0, + scrollMode, + ScrollWheelInput::DeltaTypeForDeltaMode( + wheelEvent.mDeltaMode), + origin, + wheelEvent.mDeltaX, wheelEvent.mDeltaY, + wheelEvent.mAllowToOverrideSystemScrollSpeed); + + // We add the user multiplier as a separate field, rather than premultiplying + // it, because if the input is converted back to a WidgetWheelEvent, then + // EventStateManager would apply the delta a second time. We could in theory + // work around this by asking ESM to customize the event much sooner, and + // then save the "mCustomizedByUserPrefs" bit on ScrollWheelInput - but for + // now, this seems easier. + EventStateManager::GetUserPrefsForWheelEvent(&wheelEvent, + &input.mUserDeltaMultiplierX, + &input.mUserDeltaMultiplierY); + + nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId); + wheelEvent.mRefPoint.x = input.mOrigin.x; + wheelEvent.mRefPoint.y = input.mOrigin.y; + wheelEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ; + return status; + } + + UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage); + TransformEventRefPoint(&aEvent.mRefPoint, aOutTargetGuid); + return nsEventStatus_eIgnore; + + } + default: { + + UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage); + TransformEventRefPoint(&aEvent.mRefPoint, aOutTargetGuid); + return nsEventStatus_eIgnore; + + } + } + + MOZ_ASSERT_UNREACHABLE("Invalid WidgetInputEvent type."); + return nsEventStatus_eConsumeNoDefault; +} + +} // namespace layers +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/public/IAPZCTreeManager.h @@ -0,0 +1,223 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=99: */ +/* 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_layers_IAPZCTreeManager_h +#define mozilla_layers_IAPZCTreeManager_h + +#include <stdint.h> // for uint64_t, uint32_t + +#include "FrameMetrics.h" // for FrameMetrics, etc +#include "mozilla/EventForwards.h" // for WidgetInputEvent, nsEventStatus +#include "mozilla/layers/APZUtils.h" // for HitTestResult +#include "nsTArrayForwardDeclare.h" // for nsTArray, nsTArray_Impl, etc +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "Units.h" // for CSSPoint, CSSRect, etc + +namespace mozilla { +class InputData; + +namespace layers { + +enum AllowedTouchBehavior { + NONE = 0, + VERTICAL_PAN = 1 << 0, + HORIZONTAL_PAN = 1 << 1, + PINCH_ZOOM = 1 << 2, + DOUBLE_TAP_ZOOM = 1 << 3, + UNKNOWN = 1 << 4 +}; + +enum ZoomToRectBehavior : uint32_t { + DEFAULT_BEHAVIOR = 0, + DISABLE_ZOOM_OUT = 1 << 0, + PAN_INTO_VIEW_ONLY = 1 << 1, + ONLY_ZOOM_TO_DEFAULT_SCALE = 1 << 2 +}; + +class AsyncDragMetrics; + +class IAPZCTreeManager { + NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(IAPZCTreeManager) + +public: + + /** + * General handler for incoming input events. Manipulates the frame metrics + * based on what type of input it is. For example, a PinchGestureEvent will + * cause scaling. This should only be called externally to this class, and + * must be called on the controller thread. + * + * This function transforms |aEvent| to have its coordinates in DOM space. + * This is so that the event can be passed through the DOM and content can + * handle them. The event may need to be converted to a WidgetInputEvent + * by the caller if it wants to do this. + * + * The following values may be returned by this function: + * nsEventStatus_eConsumeNoDefault is returned to indicate the + * APZ is consuming this event and the caller should discard the event with + * extreme prejudice. The exact scenarios under which this is returned is + * implementation-dependent and may vary. + * nsEventStatus_eIgnore is returned to indicate that the APZ code didn't + * use this event. This might be because it was directed at a point on + * the screen where there was no APZ, or because the thing the user was + * trying to do was not allowed. (For example, attempting to pan a + * non-pannable document). + * nsEventStatus_eConsumeDoDefault is returned to indicate that the APZ + * code may have used this event to do some user-visible thing. Note that + * in some cases CONSUMED is returned even if the event was NOT used. This + * is because we cannot always know at the time of event delivery whether + * the event will be used or not. So we err on the side of sending + * CONSUMED when we are uncertain. + * + * @param aEvent input event object; is modified in-place + * @param aOutTargetGuid returns the guid of the apzc this event was + * delivered to. May be null. + * @param aOutInputBlockId returns the id of the input block that this event + * was added to, if that was the case. May be null. + */ + virtual nsEventStatus ReceiveInputEvent( + InputData& aEvent, + ScrollableLayerGuid* aOutTargetGuid, + uint64_t* aOutInputBlockId) = 0; + + /** + * WidgetInputEvent handler. Transforms |aEvent| (which is assumed to be an + * already-existing instance of an WidgetInputEvent which may be an + * WidgetTouchEvent) to have its coordinates in DOM space. This is so that the + * event can be passed through the DOM and content can handle them. + * + * NOTE: Be careful of invoking the WidgetInputEvent variant. This can only be + * called on the main thread. See widget/InputData.h for more information on + * why we have InputData and WidgetInputEvent separated. If this function is + * used, the controller thread must be the main thread, or undefined behaviour + * may occur. + * NOTE: On unix, mouse events are treated as touch and are forwarded + * to the appropriate apz as such. + * + * See documentation for other ReceiveInputEvent above. + */ + nsEventStatus ReceiveInputEvent( + WidgetInputEvent& aEvent, + ScrollableLayerGuid* aOutTargetGuid, + uint64_t* aOutInputBlockId); + + /** + * Kicks an animation to zoom to a rect. This may be either a zoom out or zoom + * in. The actual animation is done on the compositor thread after being set + * up. |aRect| must be given in CSS pixels, relative to the document. + * |aFlags| is a combination of the ZoomToRectBehavior enum values. + */ + virtual void ZoomToRect( + const ScrollableLayerGuid& aGuid, + const CSSRect& aRect, + const uint32_t aFlags = DEFAULT_BEHAVIOR) = 0; + + /** + * If we have touch listeners, this should always be called when we know + * definitively whether or not content has preventDefaulted any touch events + * that have come in. If |aPreventDefault| is true, any touch events in the + * queue will be discarded. This function must be called on the controller + * thread. + */ + virtual void ContentReceivedInputBlock( + uint64_t aInputBlockId, + bool aPreventDefault) = 0; + + /** + * When the event regions code is enabled, this function should be invoked to + * to confirm the target of the input block. This is only needed in cases + * where the initial input event of the block hit a dispatch-to-content region + * but is safe to call for all input blocks. This function should always be + * invoked on the controller thread. + * The different elements in the array of targets correspond to the targets + * for the different touch points. In the case where the touch point has no + * target, or the target is not a scrollable frame, the target's |mScrollId| + * should be set to FrameMetrics::NULL_SCROLL_ID. + */ + virtual void SetTargetAPZC( + uint64_t aInputBlockId, + const nsTArray<ScrollableLayerGuid>& aTargets) = 0; + + /** + * Updates any zoom constraints contained in the <meta name="viewport"> tag. + * If the |aConstraints| is Nothing() then previously-provided constraints for + * the given |aGuid| are cleared. + */ + virtual void UpdateZoomConstraints( + const ScrollableLayerGuid& aGuid, + const Maybe<ZoomConstraints>& aConstraints) = 0; + + /** + * Cancels any currently running animation. Note that all this does is set the + * state of the AsyncPanZoomController back to NOTHING, but it is the + * animation's responsibility to check this before advancing. + */ + virtual void CancelAnimation(const ScrollableLayerGuid &aGuid) = 0; + + /** + * Adjusts the root APZC to compensate for a shift in the surface. See the + * documentation on AsyncPanZoomController::AdjustScrollForSurfaceShift for + * some more details. This is only currently needed due to surface shifts + * caused by the dynamic toolbar on Android. + */ + virtual void AdjustScrollForSurfaceShift(const ScreenPoint& aShift) = 0; + + virtual void SetDPI(float aDpiValue) = 0; + + /** + * Sets allowed touch behavior values for current touch-session for specific + * input block (determined by aInputBlock). + * Should be invoked by the widget. Each value of the aValues arrays + * corresponds to the different touch point that is currently active. + * Must be called after receiving the TOUCH_START event that starts the + * touch-session. + * This must be called on the controller thread. + */ + virtual void SetAllowedTouchBehavior( + uint64_t aInputBlockId, + const nsTArray<TouchBehaviorFlags>& aValues) = 0; + + virtual void StartScrollbarDrag( + const ScrollableLayerGuid& aGuid, + const AsyncDragMetrics& aDragMetrics) = 0; + + /** + * Function used to disable LongTap gestures. + * + * On slow running tests, drags and touch events can be misinterpreted + * as a long tap. This allows tests to disable long tap gesture detection. + */ + virtual void SetLongTapEnabled(bool aTapGestureEnabled) = 0; + + /** + * Process touch velocity. + * Sometimes the touch move event will have a velocity even though no scrolling + * is occurring such as when the toolbar is being hidden/shown in Fennec. + * This function can be called to have the y axis' velocity queue updated. + */ + virtual void ProcessTouchVelocity(uint32_t aTimestampMs, float aSpeedY) = 0; + +protected: + + // Methods to help process WidgetInputEvents (or manage conversion to/from InputData) + + virtual void TransformEventRefPoint( + LayoutDeviceIntPoint* aRefPoint, + ScrollableLayerGuid* aOutTargetGuid) = 0; + + virtual void UpdateWheelTransaction( + LayoutDeviceIntPoint aRefPoint, + EventMessage aEventMessage) = 0; + + // Discourage destruction outside of decref + + virtual ~IAPZCTreeManager() { } +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_IAPZCTreeManager_h
--- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -600,40 +600,16 @@ APZCTreeManager::UpdateHitTestingTree(Tr gfx::TreeAutoIndent indent(mApzcTreeLog); next = UpdateHitTestingTree(aState, child, childLayersId, ancestorTransform, aParent, next); } return node; } -// Returns whether or not a wheel event action will be (or was) performed by -// APZ. If this returns true, the event must not perform a synchronous -// scroll. -// -// Even if this returns false, all wheel events in APZ-aware widgets must -// be sent through APZ so they are transformed correctly for TabParent. -static bool -WillHandleWheelEvent(WidgetWheelEvent* aEvent) -{ - return EventStateManager::WheelEventIsScrollAction(aEvent) && - (aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE || - aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL || - aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PAGE); -} - -static bool -WillHandleMouseEvent(const WidgetMouseEventBase& aEvent) -{ - return aEvent.mMessage == eMouseMove || - aEvent.mMessage == eMouseDown || - aEvent.mMessage == eMouseUp || - aEvent.mMessage == eDragEnd; -} - template<typename PanGestureOrScrollWheelInput> static bool WillHandleInput(const PanGestureOrScrollWheelInput& aPanInput) { if (!NS_IsMainThread()) { return true; } @@ -884,16 +860,19 @@ APZCTreeManager::ReceiveInputEvent(Input /* aTargetConfirmed = */ hitResult != HitDispatchToContentRegion, tapInput, aOutInputBlockId); // Update the out-parameters so they are what the caller expects. apzc->GetGuid(aOutTargetGuid); tapInput.mPoint = *untransformedPoint; } break; + } case SENTINEL_INPUT: { + MOZ_ASSERT_UNREACHABLE("Invalid InputType."); + break; } } return result; } static TouchBehaviorFlags ConvertToTouchBehavior(HitTestResult result) { @@ -1034,17 +1013,17 @@ APZCTreeManager::ProcessTouchInput(Multi } // For computing the event to pass back to Gecko, use up-to-date transforms // (i.e. not anything cached in an input block). // This ensures that transformToApzc and transformToGecko are in sync. ScreenToParentLayerMatrix4x4 transformToApzc = GetScreenToApzcTransform(mApzcForInputBlock); ParentLayerToScreenMatrix4x4 transformToGecko = GetApzcToGeckoTransform(mApzcForInputBlock); ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko; - + for (size_t i = 0; i < aInput.mTouches.Length(); i++) { SingleTouchData& touchData = aInput.mTouches[i]; Maybe<ScreenIntPoint> untransformedScreenPoint = UntransformBy( outTransform, touchData.mScreenPoint); if (!untransformedScreenPoint) { return nsEventStatus_eIgnore; } touchData.mScreenPoint = *untransformedScreenPoint; @@ -1060,42 +1039,41 @@ APZCTreeManager::ProcessTouchInput(Multi mHitResultForInputBlock = HitNothing; mRetainedTouchIdentifier = -1; } return result; } void -APZCTreeManager::UpdateWheelTransaction(WidgetInputEvent& aEvent) +APZCTreeManager::UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint, + EventMessage aEventMessage) { WheelBlockState* txn = mInputQueue->GetCurrentWheelTransaction(); if (!txn) { return; } // If the transaction has simply timed out, we don't need to do anything // else. if (txn->MaybeTimeout(TimeStamp::Now())) { return; } - switch (aEvent.mMessage) { + switch (aEventMessage) { case eMouseMove: case eDragOver: { - WidgetMouseEvent* mouseEvent = aEvent.AsMouseEvent(); - if (!mouseEvent->IsReal()) { - return; - } - ScreenIntPoint point = - ViewAs<ScreenPixel>(aEvent.mRefPoint, - PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent); - txn->OnMouseMove(point); - return; + ScreenIntPoint point = + ViewAs<ScreenPixel>(aRefPoint, + PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent); + + txn->OnMouseMove(point); + + return; } case eKeyPress: case eKeyUp: case eKeyDown: case eMouseUp: case eMouseDown: case eMouseDoubleClick: case eMouseClick: @@ -1103,177 +1081,50 @@ APZCTreeManager::UpdateWheelTransaction( case eDrop: txn->EndTransaction(); return; default: break; } } -nsEventStatus -APZCTreeManager::ProcessEvent(WidgetInputEvent& aEvent, - ScrollableLayerGuid* aOutTargetGuid, - uint64_t* aOutInputBlockId) +void +APZCTreeManager::TransformEventRefPoint(LayoutDeviceIntPoint* aRefPoint, + ScrollableLayerGuid* aOutTargetGuid) { - MOZ_ASSERT(NS_IsMainThread()); - nsEventStatus result = nsEventStatus_eIgnore; - - // Note, we call this before having transformed the reference point. - UpdateWheelTransaction(aEvent); - - // Transform the mRefPoint. + // Transform the aRefPoint. // If the event hits an overscrolled APZC, instruct the caller to ignore it. HitTestResult hitResult = HitNothing; PixelCastJustification LDIsScreen = PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent; ScreenIntPoint refPointAsScreen = - ViewAs<ScreenPixel>(aEvent.mRefPoint, LDIsScreen); + ViewAs<ScreenPixel>(*aRefPoint, LDIsScreen); RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(refPointAsScreen, &hitResult); if (apzc) { MOZ_ASSERT(hitResult != HitNothing); apzc->GetGuid(aOutTargetGuid); ScreenToParentLayerMatrix4x4 transformToApzc = GetScreenToApzcTransform(apzc); ParentLayerToScreenMatrix4x4 transformToGecko = GetApzcToGeckoTransform(apzc); ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko; Maybe<ScreenIntPoint> untransformedRefPoint = UntransformBy(outTransform, refPointAsScreen); if (untransformedRefPoint) { - aEvent.mRefPoint = + *aRefPoint = ViewAs<LayoutDevicePixel>(*untransformedRefPoint, LDIsScreen); } } - return result; -} - -nsEventStatus -APZCTreeManager::ProcessMouseEvent(WidgetMouseEventBase& aEvent, - ScrollableLayerGuid* aOutTargetGuid, - uint64_t* aOutInputBlockId) -{ - MOZ_ASSERT(NS_IsMainThread()); - - // Note, we call this before having transformed the reference point. - UpdateWheelTransaction(aEvent); - - MouseInput input(aEvent); - input.mOrigin = ScreenPoint(aEvent.mRefPoint.x, aEvent.mRefPoint.y); - - nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId); - - aEvent.mRefPoint.x = input.mOrigin.x; - aEvent.mRefPoint.y = input.mOrigin.y; - aEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ; - return status; } void APZCTreeManager::ProcessTouchVelocity(uint32_t aTimestampMs, float aSpeedY) { if (mApzcForInputBlock) { mApzcForInputBlock->HandleTouchVelocity(aTimestampMs, aSpeedY); } } -nsEventStatus -APZCTreeManager::ProcessWheelEvent(WidgetWheelEvent& aEvent, - ScrollableLayerGuid* aOutTargetGuid, - uint64_t* aOutInputBlockId) -{ - ScrollWheelInput::ScrollMode scrollMode = ScrollWheelInput::SCROLLMODE_INSTANT; - if (gfxPrefs::SmoothScrollEnabled() && - ((aEvent.mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE && - gfxPrefs::WheelSmoothScrollEnabled()) || - (aEvent.mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PAGE && - gfxPrefs::PageSmoothScrollEnabled()))) - { - scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH; - } - - ScreenPoint origin(aEvent.mRefPoint.x, aEvent.mRefPoint.y); - ScrollWheelInput input(aEvent.mTime, aEvent.mTimeStamp, 0, - scrollMode, - ScrollWheelInput::DeltaTypeForDeltaMode( - aEvent.mDeltaMode), - origin, - aEvent.mDeltaX, aEvent.mDeltaY, - aEvent.mAllowToOverrideSystemScrollSpeed); - - // We add the user multiplier as a separate field, rather than premultiplying - // it, because if the input is converted back to a WidgetWheelEvent, then - // EventStateManager would apply the delta a second time. We could in theory - // work around this by asking ESM to customize the event much sooner, and - // then save the "mCustomizedByUserPrefs" bit on ScrollWheelInput - but for - // now, this seems easier. - EventStateManager::GetUserPrefsForWheelEvent(&aEvent, - &input.mUserDeltaMultiplierX, - &input.mUserDeltaMultiplierY); - - nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId); - aEvent.mRefPoint.x = input.mOrigin.x; - aEvent.mRefPoint.y = input.mOrigin.y; - aEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ; - return status; -} - -nsEventStatus -APZCTreeManager::ReceiveInputEvent(WidgetInputEvent& aEvent, - ScrollableLayerGuid* aOutTargetGuid, - uint64_t* aOutInputBlockId) -{ - // In general it is preferable to use the version of ReceiveInputEvent - // that takes an InputData, as that is usable from off-main-thread. On some - // platforms OMT input isn't possible, and there we can use this version. - - MOZ_ASSERT(NS_IsMainThread()); - APZThreadUtils::AssertOnControllerThread(); - - // Initialize aOutInputBlockId to a sane value, and then later we overwrite - // it if the input event goes into a block. - if (aOutInputBlockId) { - *aOutInputBlockId = InputBlockState::NO_BLOCK_ID; - } - - switch (aEvent.mClass) { - case eMouseEventClass: - case eDragEventClass: { - WidgetMouseEventBase& mouseEvent = *aEvent.AsMouseEventBase(); - if (WillHandleMouseEvent(mouseEvent)) { - return ProcessMouseEvent(mouseEvent, aOutTargetGuid, aOutInputBlockId); - } - return ProcessEvent(aEvent, aOutTargetGuid, aOutInputBlockId); - } - case eTouchEventClass: { - WidgetTouchEvent& touchEvent = *aEvent.AsTouchEvent(); - MultiTouchInput touchInput(touchEvent); - nsEventStatus result = ProcessTouchInput(touchInput, aOutTargetGuid, aOutInputBlockId); - // touchInput was modified in-place to possibly remove some - // touch points (if we are overscrolled), and the coordinates were - // modified using the APZ untransform. We need to copy these changes - // back into the WidgetInputEvent. - touchEvent.mTouches.Clear(); - touchEvent.mTouches.SetCapacity(touchInput.mTouches.Length()); - for (size_t i = 0; i < touchInput.mTouches.Length(); i++) { - *touchEvent.mTouches.AppendElement() = - touchInput.mTouches[i].ToNewDOMTouch(); - } - touchEvent.mFlags.mHandledByAPZ = touchInput.mHandledByAPZ; - return result; - } - case eWheelEventClass: { - WidgetWheelEvent& wheelEvent = *aEvent.AsWheelEvent(); - if (WillHandleWheelEvent(&wheelEvent)) { - return ProcessWheelEvent(wheelEvent, aOutTargetGuid, aOutInputBlockId); - } - return ProcessEvent(aEvent, aOutTargetGuid, aOutInputBlockId); - } - default: { - return ProcessEvent(aEvent, aOutTargetGuid, aOutInputBlockId); - } - } -} - void APZCTreeManager::ZoomToRect(const ScrollableLayerGuid& aGuid, const CSSRect& aRect, const uint32_t aFlags) { RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid); if (apzc) { apzc->ZoomToRect(aRect, aFlags); @@ -1757,17 +1608,17 @@ APZCTreeManager::BuildOverscrollHandoffC // Print the overscroll chain for debugging. for (uint32_t i = 0; i < result->Length(); ++i) { APZCTM_LOG("OverscrollHandoffChain[%d] = %p\n", i, result->GetApzcAtIndex(i).get()); } return result; } -/* static */ void +void APZCTreeManager::SetLongTapEnabled(bool aLongTapEnabled) { APZThreadUtils::RunOnControllerThread( NewRunnableFunction(GestureEventListener::SetLongTapEnabled, aLongTapEnabled)); } RefPtr<HitTestingTreeNode> APZCTreeManager::FindScrollNode(const AsyncDragMetrics& aDragMetrics)
--- a/gfx/layers/apz/src/APZCTreeManager.h +++ b/gfx/layers/apz/src/APZCTreeManager.h @@ -1,60 +1,38 @@ /* -*- Mode: C++; tab-width: 8; 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 mozilla_layers_APZCTreeManager_h #define mozilla_layers_APZCTreeManager_h -#include <stdint.h> // for uint64_t, uint32_t #include <map> // for std::map -#include "FrameMetrics.h" // for FrameMetrics, etc #include "gfxPoint.h" // for gfxPoint #include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 -#include "mozilla/EventForwards.h" // for WidgetInputEvent, nsEventStatus #include "mozilla/gfx/Logging.h" // for gfx::TreeLog #include "mozilla/gfx/Matrix.h" // for Matrix4x4 -#include "mozilla/layers/APZUtils.h" // for HitTestResult #include "mozilla/layers/TouchCounter.h"// for TouchCounter +#include "mozilla/layers/IAPZCTreeManager.h" // for IAPZCTreeManager #include "mozilla/Mutex.h" // for Mutex #include "mozilla/RefPtr.h" // for RefPtr #include "mozilla/TimeStamp.h" // for mozilla::TimeStamp #include "nsCOMPtr.h" // for already_AddRefed -#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc -#include "nsTArrayForwardDeclare.h" // for nsTArray, nsTArray_Impl, etc -#include "Units.h" // for CSSPoint, CSSRect, etc + namespace mozilla { -class InputData; class MultiTouchInput; namespace layers { -enum AllowedTouchBehavior { - NONE = 0, - VERTICAL_PAN = 1 << 0, - HORIZONTAL_PAN = 1 << 1, - PINCH_ZOOM = 1 << 2, - DOUBLE_TAP_ZOOM = 1 << 3, - UNKNOWN = 1 << 4