author | Taras Glek <tglek@mozilla.com> |
Mon, 07 Nov 2011 16:36:08 -0800 | |
changeset 79958 | 69f7d8cc0c00f785984183df7b4ee4b30db09bd3 |
parent 79956 | 412bc4e114ec283fd789d0a23aaeb5a168f8dd3d (diff) |
parent 79957 | 27ecabf7e55e9a9b5d9a0900e275fb50ef14e9d3 (current diff) |
child 79959 | 7ee6348181394bcabe262a9aacb34c6ac5411fb7 |
push id | 3222 |
push user | tglek@mozilla.com |
push date | Tue, 08 Nov 2011 00:36:21 +0000 |
treeherder | mozilla-inbound@69f7d8cc0c00 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | Mossop |
bugs | 668392 |
milestone | 10.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
new file mode 100644 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# .gitignore - List of filenames git should ignore + +# Filenames that should be ignored wherever they appear +*~ +*.pyc +*.pyo +TAGS +tags +ID +.DS_Store* + +# Vim swap files. +.*.sw[a-z] + +# User files that may appear at the root +.mozconfig +mozconfig +configure +config.cache +config.log + +# Empty marker file that's generated when we check out NSS +security/manager/.nss.checkout + +# Build directories +obj/* + +# Build directories for js shell +*/_DBG.OBJ/ +*/_OPT.OBJ/ + +# SpiderMonkey configury +js/src/configure +js/src/autom4te.cache +# SpiderMonkey test result logs +js/src/tests/results-*.html +js/src/tests/results-*.txt + +# Java HTML5 parser classes +parser/html/java/htmlparser/ +parser/html/java/javaparser/
--- a/.hgtags +++ b/.hgtags @@ -64,8 +64,9 @@ a71bd564ebf5bf4f93d13e84114f759c263130b0 a95d426422816513477e5863add1b00ac7041dcb AURORA_BASE_20110412 138f593553b66c9f815e8f57870c19d6347f7702 UPDATE_PACKAGING_R14 9eae975b3d6fb7748fe5a3c0113d449b1c7cc0b2 AURORA_BASE_20110524 138f593553b66c9f815e8f57870c19d6347f7702 UPDATE_PACKAGING_R14 462c726144bc1fb45b61e774f64ac5d61b4e047c UPDATE_PACKAGING_R14 5eb553dd2ceae5f88d80f27afc5ef3935c5d43b0 AURORA_BASE_20110705 41b84b87c816403e1b74963d8094cff0406c989e AURORA_BASE_20110816 c0983049bcaa9551e5f276d5a77ce154c151e0b0 AURORA_BASE_20110927 +462c726144bc1fb45b61e774f64ac5d61b4e047c UPDATE_PACKAGING_R15
--- a/Makefile.in +++ b/Makefile.in @@ -208,16 +208,18 @@ ifneq ($(OS_ARCH)_$(GNU_CC), WINNT_) # No point in clobbering if PGO has been explicitly disabled. ifndef NO_PROFILE_GUIDED_OPTIMIZE maybe_clobber_profiledbuild: clean else maybe_clobber_profiledbuild: endif else maybe_clobber_profiledbuild: + $(RM) $(DIST)/bin/*.pgc + find $(DIST)/$(MOZ_APP_NAME) -name "*.pgc" -exec mv {} $(DIST)/bin \; endif .PHONY: maybe_clobber_profiledbuild # Look for R_386_PC32 relocations in shared libs, these # break x86_64 builds and SELinux users. ifeq ($(OS_TARGET)_$(TARGET_XPCOM_ABI),Linux_x86-gcc3) scheck::
--- a/accessible/public/nsIAccessibleEvent.idl +++ b/accessible/public/nsIAccessibleEvent.idl @@ -54,17 +54,17 @@ interface nsIDOMNode; * the event and its target. To listen to in-process accessibility invents, * make your object an nsIObserver, and listen for accessible-event by * using code something like this: * nsCOMPtr<nsIObserverService> observerService = * do_GetService("@mozilla.org/observer-service;1", &rv); * if (NS_SUCCEEDED(rv)) * rv = observerService->AddObserver(this, "accessible-event", PR_TRUE); */ -[scriptable, uuid(fd1378c5-c606-4a5e-a321-8e7fc107e5cf)] +[scriptable, uuid(7f66a33a-9ed7-4fd4-87a8-e431b0f43368)] interface nsIAccessibleEvent : nsISupports { /** * An object has been created. */ const unsigned long EVENT_SHOW = 0x0001; /** @@ -275,17 +275,22 @@ interface nsIAccessibleEvent : nsISuppor const unsigned long EVENT_DOCUMENT_ATTRIBUTES_CHANGED = 0x002A; /** * The contents of the document have changed. */ const unsigned long EVENT_DOCUMENT_CONTENT_CHANGED = 0x002B; const unsigned long EVENT_PROPERTY_CHANGED = 0x002C; - const unsigned long EVENT_SELECTION_CHANGED = 0x002D; + + /** + * A slide changed in a presentation document or a page boundary was + * crossed in a word processing document. + */ + const unsigned long EVENT_PAGE_CHANGED = 0x002D; /** * A text object's attributes changed. * Also see EVENT_OBJECT_ATTRIBUTE_CHANGED. */ const unsigned long EVENT_TEXT_ATTRIBUTE_CHANGED = 0x002E; /** @@ -432,25 +437,19 @@ interface nsIAccessibleEvent : nsISuppor const unsigned long EVENT_HYPERTEXT_NLINKS_CHANGED = 0x0054; /** * An object's attributes changed. Also see EVENT_TEXT_ATTRIBUTE_CHANGED. */ const unsigned long EVENT_OBJECT_ATTRIBUTE_CHANGED = 0x0055; /** - * A slide changed in a presentation document or a page boundary was - * crossed in a word processing document. - */ - const unsigned long EVENT_PAGE_CHANGED = 0x0056; - - /** * Help make sure event map does not get out-of-line. */ - const unsigned long EVENT_LAST_ENTRY = 0x0057; + const unsigned long EVENT_LAST_ENTRY = 0x0056; /** * The type of event, based on the enumerated event values * defined in this interface. */ readonly attribute unsigned long eventType; /**
--- a/accessible/src/atk/nsAccessibleWrap.cpp +++ b/accessible/src/atk/nsAccessibleWrap.cpp @@ -1084,20 +1084,34 @@ nsAccessibleWrap::FirePlatformEvent(AccE nsCOMPtr<nsIAccessibleValue> value(do_QueryObject(accessible)); if (value) { // Make sure this is a numeric value // Don't fire for MSAA string value changes (e.g. text editing) // ATK values are always numeric g_object_notify( (GObject*)atkObj, "accessible-value" ); } } break; - case nsIAccessibleEvent::EVENT_SELECTION_CHANGED: - MAI_LOG_DEBUG(("\n\nReceived: EVENT_SELECTION_CHANGED\n")); - g_signal_emit_by_name(atkObj, "selection_changed"); - break; + case nsIAccessibleEvent::EVENT_SELECTION: + case nsIAccessibleEvent::EVENT_SELECTION_ADD: + case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: + { + // XXX: dupe events may be fired + MAI_LOG_DEBUG(("\n\nReceived: EVENT_SELECTION_CHANGED\n")); + AccSelChangeEvent* selChangeEvent = downcast_accEvent(aEvent); + g_signal_emit_by_name(nsAccessibleWrap::GetAtkObject(selChangeEvent->Widget()), + "selection_changed"); + break; + } + + case nsIAccessibleEvent::EVENT_SELECTION_WITHIN: + { + MAI_LOG_DEBUG(("\n\nReceived: EVENT_SELECTION_CHANGED\n")); + g_signal_emit_by_name(atkObj, "selection_changed"); + break; + } case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: MAI_LOG_DEBUG(("\n\nReceived: EVENT_TEXT_SELECTION_CHANGED\n")); g_signal_emit_by_name(atkObj, "text_selection_changed"); break; case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
--- a/accessible/src/base/AccEvent.cpp +++ b/accessible/src/base/AccEvent.cpp @@ -326,16 +326,38 @@ AccCaretMoveEvent::CreateXPCOMObject() { nsAccEvent* event = new nsAccCaretMoveEvent(this); NS_IF_ADDREF(event); return event; } //////////////////////////////////////////////////////////////////////////////// +// AccSelChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +AccSelChangeEvent:: + AccSelChangeEvent(nsAccessible* aWidget, nsAccessible* aItem, + SelChangeType aSelChangeType) : + AccEvent(0, aItem, eAutoDetect, eCoalesceSelectionChange), + mWidget(aWidget), mItem(aItem), mSelChangeType(aSelChangeType), + mPreceedingCount(0), mPackedEvent(nsnull) +{ + if (aSelChangeType == eSelectionAdd) { + if (mWidget->GetSelectedItem(1)) + mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD; + else + mEventType = nsIAccessibleEvent::EVENT_SELECTION; + } else { + mEventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE; + } +} + + +//////////////////////////////////////////////////////////////////////////////// // AccTableChangeEvent //////////////////////////////////////////////////////////////////////////////// AccTableChangeEvent:: AccTableChangeEvent(nsAccessible* aAccessible, PRUint32 aEventType, PRInt32 aRowOrColIndex, PRInt32 aNumRowsOrCols) : AccEvent(aEventType, aAccessible), mRowOrColIndex(aRowOrColIndex), mNumRowsOrCols(aNumRowsOrCols)
--- a/accessible/src/base/AccEvent.h +++ b/accessible/src/base/AccEvent.h @@ -77,16 +77,19 @@ public: // subtree or the same node, only the umbrella event on the ancestor // will be emitted. eCoalesceFromSameSubtree, // eCoalesceOfSameType : For events of the same type, only the newest event // will be processed. eCoalesceOfSameType, + // eCoalesceSelectionChange: coalescence of selection change events. + eCoalesceSelectionChange, + // eRemoveDupes : For repeat events, only the newest event in queue // will be emitted. eRemoveDupes, // eDoNotEmit : This event is confirmed as a duplicate, do not emit it. eDoNotEmit }; @@ -120,16 +123,17 @@ public: enum EventGroup { eGenericEvent, eStateChangeEvent, eTextChangeEvent, eMutationEvent, eHideEvent, eShowEvent, eCaretMoveEvent, + eSelectionChangeEvent, eTableChangeEvent }; static const EventGroup kEventGroup = eGenericEvent; virtual unsigned int GetEventGroups() const { return 1U << eGenericEvent; } @@ -322,20 +326,47 @@ public: private: PRInt32 mCaretOffset; }; /** * Accessible widget selection change event. */ -class AccSelectionChangeEvent : public AccEvent +class AccSelChangeEvent : public AccEvent { public: + enum SelChangeType { + eSelectionAdd, + eSelectionRemove + }; + AccSelChangeEvent(nsAccessible* aWidget, nsAccessible* aItem, + SelChangeType aSelChangeType); + + virtual ~AccSelChangeEvent() { } + + // AccEvent + static const EventGroup kEventGroup = eSelectionChangeEvent; + virtual unsigned int GetEventGroups() const + { + return AccEvent::GetEventGroups() | (1U << eSelectionChangeEvent); + } + + // AccSelChangeEvent + nsAccessible* Widget() const { return mWidget; } + +private: + nsRefPtr<nsAccessible> mWidget; + nsRefPtr<nsAccessible> mItem; + SelChangeType mSelChangeType; + PRUint32 mPreceedingCount; + AccSelChangeEvent* mPackedEvent; + + friend class NotificationController; }; /** * Accessible table change event. */ class AccTableChangeEvent : public AccEvent {
--- a/accessible/src/base/FocusManager.cpp +++ b/accessible/src/base/FocusManager.cpp @@ -36,16 +36,17 @@ * ***** END LICENSE BLOCK ***** */ #include "FocusManager.h" #include "nsAccessibilityService.h" #include "nsAccUtils.h" #include "nsRootAccessible.h" +#include "nsEventStateManager.h" #include "nsFocusManager.h" namespace dom = mozilla::dom; using namespace mozilla::a11y; FocusManager::FocusManager() { } @@ -347,30 +348,41 @@ FocusManager::ProcessFocusEvent(AccEvent // we receive focus event, for example if the node is removed from DOM. nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START, anchorJump, fromUserInputFlag); } targetDocument->SetAnchorJump(nsnull); } } -nsIContent* -FocusManager::FocusedDOMElm() const +nsINode* +FocusManager::FocusedDOMNode() const { nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager(); - return DOMFocusManager->GetFocusedContent(); -} + nsIContent* focusedElm = DOMFocusManager->GetFocusedContent(); -nsIDocument* -FocusManager::FocusedDOMDocument() const -{ - nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager(); + // No focus on remote target elements like xul:browser having DOM focus and + // residing in chrome process because it means an element in content process + // keeps the focus. + if (focusedElm) { + if (nsEventStateManager::IsRemoteTarget(focusedElm)) + return nsnull; + return focusedElm; + } + // Otherwise the focus can be on DOM document. nsCOMPtr<nsIDOMWindow> focusedWnd; DOMFocusManager->GetFocusedWindow(getter_AddRefs(focusedWnd)); if (focusedWnd) { nsCOMPtr<nsIDOMDocument> DOMDoc; focusedWnd->GetDocument(getter_AddRefs(DOMDoc)); nsCOMPtr<nsIDocument> DOMDocNode(do_QueryInterface(DOMDoc)); return DOMDocNode; } return nsnull; } + +nsIDocument* +FocusManager::FocusedDOMDocument() const +{ + nsINode* focusedNode = FocusedDOMNode(); + return focusedNode ? focusedNode->OwnerDoc() : nsnull; +}
--- a/accessible/src/base/FocusManager.h +++ b/accessible/src/base/FocusManager.h @@ -141,28 +141,17 @@ protected: private: FocusManager(const FocusManager&); FocusManager& operator =(const FocusManager&); /** * Return DOM node having DOM focus. */ - inline nsINode* FocusedDOMNode() const - { - nsINode* focusedNode = FocusedDOMElm(); - if (focusedNode) - return focusedNode; - return FocusedDOMDocument(); - } - - /** - * Return DOM element having DOM focus. - */ - nsIContent* FocusedDOMElm() const; + nsINode* FocusedDOMNode() const; /** * Return DOM document having DOM focus. */ nsIDocument* FocusedDOMDocument() const; private: nsRefPtr<nsAccessible> mActiveItem;
--- a/accessible/src/base/NotificationController.cpp +++ b/accessible/src/base/NotificationController.cpp @@ -46,16 +46,20 @@ #include "nsTextAccessible.h" #include "FocusManager.h" #include "TextUpdater.h" #include "mozilla/dom/Element.h" using namespace mozilla::a11y; +// Defines the number of selection add/remove events in the queue when they +// aren't packed into single selection within event. +const unsigned int kSelChangeCountToPack = 5; + //////////////////////////////////////////////////////////////////////////////// // NotificationCollector //////////////////////////////////////////////////////////////////////////////// NotificationController::NotificationController(nsDocAccessible* aDocument, nsIPresShell* aPresShell) : mObservingState(eNotObservingRefresh), mDocument(aDocument), mPresShell(aPresShell) @@ -486,16 +490,36 @@ NotificationController::CoalesceEvents() accEvent->mEventRule == tailEvent->mEventRule && accEvent->mNode == tailEvent->mNode) { tailEvent->mEventRule = AccEvent::eDoNotEmit; return; } } } break; // case eRemoveDupes + case AccEvent::eCoalesceSelectionChange: + { + AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent); + PRInt32 index = tail - 1; + for (; index >= 0; index--) { + AccEvent* thisEvent = mEvents[index]; + if (thisEvent->mEventRule == tailEvent->mEventRule) { + AccSelChangeEvent* thisSelChangeEvent = + downcast_accEvent(thisEvent); + + // Coalesce selection change events within same control. + if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) { + CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index); + return; + } + } + } + + } break; // eCoalesceSelectionChange + default: break; // case eAllowDupes, eDoNotEmit } // switch } void NotificationController::ApplyToSiblings(PRUint32 aStart, PRUint32 aEnd, PRUint32 aEventType, nsINode* aNode, @@ -507,16 +531,96 @@ NotificationController::ApplyToSiblings( accEvent->mEventRule != AccEvent::eDoNotEmit && accEvent->mNode && accEvent->mNode->GetNodeParent() == aNode->GetNodeParent()) { accEvent->mEventRule = aEventRule; } } } void +NotificationController::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent, + AccSelChangeEvent* aThisEvent, + PRInt32 aThisIndex) +{ + aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1; + + // Pack all preceding events into single selection within event + // when we receive too much selection add/remove events. + if (aTailEvent->mPreceedingCount >= kSelChangeCountToPack) { + aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_WITHIN; + aTailEvent->mAccessible = aTailEvent->mWidget; + aThisEvent->mEventRule = AccEvent::eDoNotEmit; + + // Do not emit any preceding selection events for same widget if they + // weren't coalesced yet. + if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) { + for (PRInt32 jdx = aThisIndex - 1; jdx >= 0; jdx--) { + AccEvent* prevEvent = mEvents[jdx]; + if (prevEvent->mEventRule == aTailEvent->mEventRule) { + AccSelChangeEvent* prevSelChangeEvent = + downcast_accEvent(prevEvent); + if (prevSelChangeEvent->mWidget == aTailEvent->mWidget) + prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit; + } + } + } + return; + } + + // Pack sequential selection remove and selection add events into + // single selection change event. + if (aTailEvent->mPreceedingCount == 1 && + aTailEvent->mItem != aThisEvent->mItem) { + if (aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd && + aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) { + aThisEvent->mEventRule = AccEvent::eDoNotEmit; + aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION; + aTailEvent->mPackedEvent = aThisEvent; + return; + } + + if (aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd && + aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) { + aTailEvent->mEventRule = AccEvent::eDoNotEmit; + aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION; + aThisEvent->mPackedEvent = aThisEvent; + return; + } + } + + // Unpack the packed selection change event because we've got one + // more selection add/remove. + if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) { + if (aThisEvent->mPackedEvent) { + aThisEvent->mPackedEvent->mEventType = + aThisEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ? + nsIAccessibleEvent::EVENT_SELECTION_ADD : + nsIAccessibleEvent::EVENT_SELECTION_REMOVE; + + aThisEvent->mPackedEvent->mEventRule = + AccEvent::eCoalesceSelectionChange; + + aThisEvent->mPackedEvent = nsnull; + } + + aThisEvent->mEventType = + aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ? + nsIAccessibleEvent::EVENT_SELECTION_ADD : + nsIAccessibleEvent::EVENT_SELECTION_REMOVE; + + return; + } + + // Convert into selection add since control has single selection but other + // selection events for this control are queued. + if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) + aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD; +} + +void NotificationController::CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent, AccHideEvent* aThisEvent) { // XXX: we need a way to ignore SplitNode and JoinNode() when they do not // affect the text within the hypertext. AccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent; if (!textEvent)
--- a/accessible/src/base/NotificationController.h +++ b/accessible/src/base/NotificationController.h @@ -246,21 +246,21 @@ private: * @param aEventRule the event rule to be applied * (should be eDoNotEmit or eAllowDupes) */ void ApplyToSiblings(PRUint32 aStart, PRUint32 aEnd, PRUint32 aEventType, nsINode* aNode, AccEvent::EEventRule aEventRule); /** - * Do not emit one of two given reorder events fired for DOM nodes in the case - * when one DOM node is in parent chain of second one. + * Coalesce two selection change events within the same select control. */ - void CoalesceReorderEventsFromSameTree(AccEvent* aAccEvent, - AccEvent* aDescendantAccEvent); + void CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent, + AccSelChangeEvent* aThisEvent, + PRInt32 aThisIndex); /** * Coalesce text change events caused by sibling hide events. */ void CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent, AccHideEvent* aThisEvent); void CoalesceTextChangeEventsFor(AccShowEvent* aTailEvent, AccShowEvent* aThisEvent);
--- a/accessible/src/base/nsAccessNode.cpp +++ b/accessible/src/base/nsAccessNode.cpp @@ -49,17 +49,17 @@ #include "nsApplicationAccessibleWrap.h" #include "nsIAccessibleDocument.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIDocument.h" #include "nsIDOMCSSPrimitiveValue.h" #include "nsIDOMDocument.h" #include "nsIDOMElement.h" -#include "nsIDOMNSHTMLElement.h" +#include "nsIDOMHTMLElement.h" #include "nsIDOMWindow.h" #include "nsPIDOMWindow.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIFrame.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsPresContext.h" #include "nsIPresShell.h" @@ -332,20 +332,20 @@ nsAccessNode::GetRootDocument(nsIAccessi return NS_OK; } NS_IMETHODIMP nsAccessNode::GetInnerHTML(nsAString& aInnerHTML) { aInnerHTML.Truncate(); - nsCOMPtr<nsIDOMNSHTMLElement> domNSElement(do_QueryInterface(mContent)); - NS_ENSURE_TRUE(domNSElement, NS_ERROR_NULL_POINTER); + nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(mContent); + NS_ENSURE_TRUE(htmlElement, NS_ERROR_NULL_POINTER); - return domNSElement->GetInnerHTML(aInnerHTML); + return htmlElement->GetInnerHTML(aInnerHTML); } NS_IMETHODIMP nsAccessNode::ScrollTo(PRUint32 aScrollType) { if (IsDefunct()) return NS_ERROR_FAILURE;
--- a/accessible/src/base/nsAccessibilityService.h +++ b/accessible/src/base/nsAccessibilityService.h @@ -468,17 +468,17 @@ static const char kEventTypeNames[][40] "minimize start", // EVENT_MINIMIZE_START "minimize end", // EVENT_MINIMIZE_END "document load complete", // EVENT_DOCUMENT_LOAD_COMPLETE "document reload", // EVENT_DOCUMENT_RELOAD "document load stopped", // EVENT_DOCUMENT_LOAD_STOPPED "document attributes changed", // EVENT_DOCUMENT_ATTRIBUTES_CHANGED "document content changed", // EVENT_DOCUMENT_CONTENT_CHANGED "property changed", // EVENT_PROPERTY_CHANGED - "selection changed", // EVENT_SELECTION_CHANGED + "page changed", // EVENT_PAGE_CHANGED "text attribute changed", // EVENT_TEXT_ATTRIBUTE_CHANGED "text caret moved", // EVENT_TEXT_CARET_MOVED "text changed", // EVENT_TEXT_CHANGED "text inserted", // EVENT_TEXT_INSERTED "text removed", // EVENT_TEXT_REMOVED "text updated", // EVENT_TEXT_UPDATED "text selection changed", // EVENT_TEXT_SELECTION_CHANGED "visible data changed", // EVENT_VISIBLE_DATA_CHANGED @@ -509,17 +509,16 @@ static const char kEventTypeNames[][40] "hyperlink number of anchors changed", // EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED "hyperlink selected link changed", // EVENT_HYPERLINK_SELECTED_LINK_CHANGED "hypertext link activated", // EVENT_HYPERTEXT_LINK_ACTIVATED "hypertext link selected", // EVENT_HYPERTEXT_LINK_SELECTED "hyperlink start index changed", // EVENT_HYPERLINK_START_INDEX_CHANGED "hypertext changed", // EVENT_HYPERTEXT_CHANGED "hypertext links count changed", // EVENT_HYPERTEXT_NLINKS_CHANGED "object attribute changed", // EVENT_OBJECT_ATTRIBUTE_CHANGED - "page changed" // EVENT_PAGE_CHANGED }; /** * Map nsIAccessibleRelation constants to strings. Used by * nsIAccessibleRetrieval::getStringRelationType() method. */ static const char kRelationTypeNames[][20] = { "unknown", // RELATION_NUL
--- a/accessible/src/base/nsAccessible.cpp +++ b/accessible/src/base/nsAccessible.cpp @@ -57,17 +57,17 @@ #include "States.h" #include "nsIDOMElement.h" #include "nsIDOMDocument.h" #include "nsIDOMDocumentXBL.h" #include "nsIDOMHTMLDocument.h" #include "nsIDOMHTMLFormElement.h" #include "nsIDOMNodeFilter.h" -#include "nsIDOMNSHTMLElement.h" +#include "nsIDOMHTMLElement.h" #include "nsIDOMTreeWalker.h" #include "nsIDOMXULButtonElement.h" #include "nsIDOMXULDocument.h" #include "nsIDOMXULElement.h" #include "nsIDOMXULLabelElement.h" #include "nsIDOMXULSelectCntrlEl.h" #include "nsIDOMXULSelectCntrlItemEl.h" #include "nsPIDOMWindow.h" @@ -649,17 +649,17 @@ nsAccessible::IsVisible(bool* aIsOffscre if (isEmpty && !(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) { // Consider zero area objects hidden unless they are absolutely positioned // or floating and may have descendants that have a non-zero size return false; } } // The frame intersects the viewport, but we need to check the parent view chain :( - bool isVisible = nsCoreUtils::CheckVisibilityInParentChain(frame); + bool isVisible = frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY); if (isVisible && rectVisibility == nsRectVisibility_kVisible) { *aIsOffscreen = false; } return isVisible; } PRUint64 nsAccessible::NativeState() @@ -1419,17 +1419,17 @@ nsAccessible::GetAttributesInternal(nsIP // Expose 'text-indent' attribute. rv = GetComputedStyleValue(EmptyString(), NS_LITERAL_STRING("text-indent"), value); if (NS_SUCCEEDED(rv)) nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textIndent, value); // Expose draggable object attribute? - nsCOMPtr<nsIDOMNSHTMLElement> htmlElement = do_QueryInterface(mContent); + nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(mContent); if (htmlElement) { bool draggable = false; htmlElement->GetDraggable(&draggable); if (draggable) { nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::draggable, NS_LITERAL_STRING("true")); } }
--- a/accessible/src/base/nsBaseWidgetAccessible.cpp +++ b/accessible/src/base/nsBaseWidgetAccessible.cpp @@ -40,17 +40,16 @@ #include "nsBaseWidgetAccessible.h" #include "States.h" #include "nsAccessibilityService.h" #include "nsAccUtils.h" #include "nsCoreUtils.h" #include "nsHyperTextAccessibleWrap.h" -#include "nsIDOMNSHTMLElement.h" #include "nsGUIEvent.h" #include "nsILink.h" #include "nsIFrame.h" #include "nsINameSpaceManager.h" #include "nsIURI.h" using namespace mozilla::a11y;
--- a/accessible/src/base/nsCoreUtils.cpp +++ b/accessible/src/base/nsCoreUtils.cpp @@ -753,41 +753,16 @@ nsCoreUtils::IsColumnHidden(nsITreeColum { nsCOMPtr<nsIDOMElement> element; aColumn->GetElement(getter_AddRefs(element)); nsCOMPtr<nsIContent> content = do_QueryInterface(element); return content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, nsGkAtoms::_true, eCaseMatters); } -bool -nsCoreUtils::CheckVisibilityInParentChain(nsIFrame* aFrame) -{ - nsIView* view = aFrame->GetClosestView(); - if (view && !view->IsEffectivelyVisible()) - return false; - - nsIPresShell* presShell = aFrame->PresContext()->GetPresShell(); - while (presShell) { - if (!presShell->IsActive()) { - return false; - } - - nsIFrame* rootFrame = presShell->GetRootFrame(); - presShell = nsnull; - if (rootFrame) { - nsIFrame* frame = nsLayoutUtils::GetCrossDocParentFrame(rootFrame); - if (frame) { - presShell = frame->PresContext()->GetPresShell(); - } - } - } - return true; -} - //////////////////////////////////////////////////////////////////////////////// // nsAccessibleDOMStringList //////////////////////////////////////////////////////////////////////////////// NS_IMPL_ISUPPORTS1(nsAccessibleDOMStringList, nsIDOMDOMStringList) NS_IMETHODIMP nsAccessibleDOMStringList::Item(PRUint32 aIndex, nsAString& aResult)
--- a/accessible/src/base/nsCoreUtils.h +++ b/accessible/src/base/nsCoreUtils.h @@ -365,21 +365,16 @@ public: * Return true if the given node is table header element. */ static bool IsHTMLTableHeader(nsIContent *aContent) { return aContent->NodeInfo()->Equals(nsGkAtoms::th) || aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scope); } - /** - * Check the visibility across both parent content and chrome. - */ - static bool CheckVisibilityInParentChain(nsIFrame* aFrame); - }; /** * nsIDOMDOMStringList implementation. */ class nsAccessibleDOMStringList : public nsIDOMDOMStringList {
--- a/accessible/src/base/nsDocAccessible.cpp +++ b/accessible/src/base/nsDocAccessible.cpp @@ -306,17 +306,18 @@ nsDocAccessible::NativeState() state |= states::STALE; // Expose state busy until the document and all its subdocuments is completely // loaded. if (!HasLoadState(eCompletelyLoaded)) state |= states::BUSY; nsIFrame* frame = GetFrame(); - if (!frame || !nsCoreUtils::CheckVisibilityInParentChain(frame)) { + if (!frame || + !frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) { state |= states::INVISIBLE | states::OFFSCREEN; } nsCOMPtr<nsIEditor> editor; GetAssociatedEditor(getter_AddRefs(editor)); state |= editor ? states::EDITABLE : states::READONLY; return state; @@ -1036,42 +1037,33 @@ nsDocAccessible::AttributeChangedImpl(ns if (aAttribute == nsGkAtoms::aria_busy) { bool isOn = aContent->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true, eCaseMatters); nsRefPtr<AccEvent> event = new AccStateChangeEvent(aContent, states::BUSY, isOn); FireDelayedAccessibleEvent(event); return; } - if (aAttribute == nsGkAtoms::selected || + // ARIA or XUL selection + if ((aContent->IsXUL() && aAttribute == nsGkAtoms::selected) || aAttribute == nsGkAtoms::aria_selected) { - // ARIA or XUL selection + nsAccessible* item = GetAccessible(aContent); + nsAccessible* widget = + nsAccUtils::GetSelectableContainer(item, item->State()); + if (widget) { + AccSelChangeEvent::SelChangeType selChangeType = + aContent->AttrValueIs(aNameSpaceID, aAttribute, + nsGkAtoms::_true, eCaseMatters) ? + AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove; - nsAccessible *multiSelect = - nsAccUtils::GetMultiSelectableContainer(aContent); - // XXX: Multi selects are handled here only (bug 414302). - if (multiSelect) { - // Need to find the right event to use here, SELECTION_WITHIN would - // seem right but we had started using it for something else - FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, - multiSelect->GetNode(), - AccEvent::eAllowDupes); - - static nsIContent::AttrValuesArray strings[] = - {&nsGkAtoms::_empty, &nsGkAtoms::_false, nsnull}; - if (aContent->FindAttrValueIn(kNameSpaceID_None, aAttribute, - strings, eCaseMatters) >= 0) { - FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_REMOVE, - aContent); - return; - } - - FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_ADD, - aContent); + nsRefPtr<AccEvent> event = + new AccSelChangeEvent(widget, item, selChangeType); + FireDelayedAccessibleEvent(event); } + return; } if (aAttribute == nsGkAtoms::contenteditable) { nsRefPtr<AccEvent> editableChangeEvent = new AccStateChangeEvent(aContent, states::EDITABLE); FireDelayedAccessibleEvent(editableChangeEvent); return; } @@ -1203,17 +1195,28 @@ void nsDocAccessible::ContentAppended(ns { } void nsDocAccessible::ContentStateChanged(nsIDocument* aDocument, nsIContent* aContent, nsEventStates aStateMask) { if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) { - nsHTMLSelectOptionAccessible::SelectionChangedIfOption(aContent); + nsAccessible* item = GetAccessible(aContent); + if (item) { + nsAccessible* widget = item->ContainerWidget(); + if (widget && widget->IsSelect()) { + AccSelChangeEvent::SelChangeType selChangeType = + aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED) ? + AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove; + nsRefPtr<AccEvent> event = new AccSelChangeEvent(widget, item, + selChangeType); + FireDelayedAccessibleEvent(event); + } + } } if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) { nsRefPtr<AccEvent> event = new AccStateChangeEvent(aContent, states::INVALID, true); FireDelayedAccessibleEvent(event); } }
--- a/accessible/src/base/nsRootAccessible.cpp +++ b/accessible/src/base/nsRootAccessible.cpp @@ -468,16 +468,18 @@ nsRootAccessible::ProcessDOMEvent(nsIDOM nsRefPtr<AccEvent> accEvent = new AccStateChangeEvent(accessible, states::EXPANDED, isEnabled); nsEventShell::FireEvent(accEvent); return; } if (treeItemAccessible && eventType.EqualsLiteral("select")) { + // XXX: We shouldn't be based on DOM select event which doesn't provide us + // any context info. We should integrate into nsTreeSelection instead. // If multiselect tree, we should fire selectionadd or selection removed if (FocusMgr()->HasDOMFocus(targetNode)) { nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel = do_QueryInterface(targetNode); nsAutoString selType; multiSel->GetSelType(selType); if (selType.IsEmpty() || !selType.EqualsLiteral("single")) { // XXX: We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE
--- a/accessible/src/html/nsHTMLFormControlAccessible.cpp +++ b/accessible/src/html/nsHTMLFormControlAccessible.cpp @@ -41,17 +41,16 @@ #include "Relation.h" #include "States.h" #include "nsAccUtils.h" #include "nsTextEquivUtils.h" #include "nsIAccessibleRelation.h" #include "nsIDOMDocument.h" #include "nsIDOMHTMLInputElement.h" -#include "nsIDOMNSHTMLElement.h" #include "nsIDOMNSEditableElement.h" #include "nsIDOMHTMLFormElement.h" #include "nsIDOMHTMLLegendElement.h" #include "nsIDOMHTMLTextAreaElement.h" #include "nsIDOMNodeList.h" #include "nsIEditor.h" #include "nsIFrame.h" #include "nsINameSpaceManager.h"
--- a/accessible/src/html/nsHTMLSelectAccessible.cpp +++ b/accessible/src/html/nsHTMLSelectAccessible.cpp @@ -411,69 +411,17 @@ nsHTMLSelectOptionAccessible::SetSelecte } //////////////////////////////////////////////////////////////////////////////// // nsHTMLSelectOptionAccessible: Widgets nsAccessible* nsHTMLSelectOptionAccessible::ContainerWidget() const { - if (mParent && mParent->IsListControl()) { - nsAccessible* grandParent = mParent->Parent(); - if (grandParent && grandParent->IsCombobox()) - return grandParent; - - return mParent; - } - return nsnull; -} - -//////////////////////////////////////////////////////////////////////////////// -// nsHTMLSelectOptionAccessible: static methods - -void -nsHTMLSelectOptionAccessible::SelectionChangedIfOption(nsIContent *aPossibleOptionNode) -{ - if (!aPossibleOptionNode || - aPossibleOptionNode->Tag() != nsGkAtoms::option || - !aPossibleOptionNode->IsHTML()) { - return; - } - - nsAccessible *multiSelect = - nsAccUtils::GetMultiSelectableContainer(aPossibleOptionNode); - if (!multiSelect) - return; - - nsAccessible *option = GetAccService()->GetAccessible(aPossibleOptionNode); - if (!option) - return; - - - nsRefPtr<AccEvent> selWithinEvent = - new AccEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, multiSelect); - - if (!selWithinEvent) - return; - - option->GetDocAccessible()->FireDelayedAccessibleEvent(selWithinEvent); - - PRUint64 state = option->State(); - PRUint32 eventType; - if (state & states::SELECTED) { - eventType = nsIAccessibleEvent::EVENT_SELECTION_ADD; - } - else { - eventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE; - } - - nsRefPtr<AccEvent> selAddRemoveEvent = new AccEvent(eventType, option); - - if (selAddRemoveEvent) - option->GetDocAccessible()->FireDelayedAccessibleEvent(selAddRemoveEvent); + return mParent && mParent->IsListControl() ? mParent : nsnull; } //////////////////////////////////////////////////////////////////////////////// // nsHTMLSelectOptionAccessible: private methods nsIContent* nsHTMLSelectOptionAccessible::GetSelectState(PRUint64* aState) { @@ -733,36 +681,32 @@ nsHTMLComboboxAccessible::AreItemsOperab { nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(GetFrame()); return comboboxFrame && comboboxFrame->IsDroppedDown(); } nsAccessible* nsHTMLComboboxAccessible::CurrentItem() { - // No current item for collapsed combobox. - return SelectedOption(true); + return AreItemsOperable() ? mListAccessible->CurrentItem() : nsnull; } //////////////////////////////////////////////////////////////////////////////// // nsHTMLComboboxAccessible: protected nsAccessible* -nsHTMLComboboxAccessible::SelectedOption(bool aIgnoreIfCollapsed) const +nsHTMLComboboxAccessible::SelectedOption() const { nsIFrame* frame = GetFrame(); nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(frame); - if (comboboxFrame) { - if (aIgnoreIfCollapsed && !comboboxFrame->IsDroppedDown()) - return nsnull; + if (!comboboxFrame) + return nsnull; - frame = comboboxFrame->GetDropDown(); - } - - nsIListControlFrame* listControlFrame = do_QueryFrame(frame); + nsIListControlFrame* listControlFrame = + do_QueryFrame(comboboxFrame->GetDropDown()); if (listControlFrame) { nsCOMPtr<nsIContent> activeOptionNode = listControlFrame->GetCurrentOption(); if (activeOptionNode) { nsDocAccessible* document = GetDocAccessible(); if (document) return document->GetAccessible(activeOptionNode); } } @@ -853,8 +797,24 @@ void nsHTMLComboboxListAccessible::GetBo if (!frame) { *aBoundingFrame = nsnull; return; } *aBoundingFrame = frame->GetParent(); aBounds = (*aBoundingFrame)->GetRect(); } + +//////////////////////////////////////////////////////////////////////////////// +// nsHTMLComboboxListAccessible: Widgets + +bool +nsHTMLComboboxListAccessible::IsActiveWidget() const +{ + return mParent && mParent->IsActiveWidget(); +} + +bool +nsHTMLComboboxListAccessible::AreItemsOperable() const +{ + return mParent && mParent->AreItemsOperable(); +} +
--- a/accessible/src/html/nsHTMLSelectAccessible.h +++ b/accessible/src/html/nsHTMLSelectAccessible.h @@ -125,18 +125,16 @@ public: PRInt32 *aSetSize); // ActionAccessible virtual PRUint8 ActionCount(); // Widgets virtual nsAccessible* ContainerWidget() const; - static void SelectionChangedIfOption(nsIContent *aPossibleOption); - protected: // nsAccessible virtual nsIFrame* GetBoundsFrame(); private: /** * Get Select element's accessible state @@ -214,17 +212,17 @@ public: protected: // nsAccessible virtual void CacheChildren(); /** * Return selected option. */ - nsAccessible* SelectedOption(bool aIgnoreIfCollapsed = false) const; + nsAccessible* SelectedOption() const; private: nsRefPtr<nsHTMLComboboxListAccessible> mListAccessible; }; /* * A class that represents the window that lives to the right * of the drop down button inside the Select. This is the window @@ -241,11 +239,15 @@ public: // nsAccessNode virtual nsIFrame* GetFrame() const; virtual bool IsPrimaryForNode() const; // nsAccessible virtual PRUint64 NativeState(); virtual void GetBoundsRect(nsRect& aBounds, nsIFrame** aBoundingFrame); + + // Widgets + virtual bool IsActiveWidget() const; + virtual bool AreItemsOperable() const; }; #endif
--- a/accessible/src/mac/mozAccessible.mm +++ b/accessible/src/mac/mozAccessible.mm @@ -185,19 +185,17 @@ GetNativeFromGeckoAccessible(nsIAccessib if (!generalAttributes) { // standard attributes that are shared and supported by all generic elements. generalAttributes = [[NSArray alloc] initWithObjects: NSAccessibilityChildrenAttribute, NSAccessibilityParentAttribute, NSAccessibilityRoleAttribute, NSAccessibilityTitleAttribute, NSAccessibilityValueAttribute, NSAccessibilitySubroleAttribute, -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 NSAccessibilityRoleDescriptionAttribute, -#endif NSAccessibilityPositionAttribute, NSAccessibilityEnabledAttribute, NSAccessibilitySizeAttribute, NSAccessibilityWindowAttribute, NSAccessibilityFocusedAttribute, NSAccessibilityHelpAttribute, NSAccessibilityTitleUIElementAttribute, kTopLevelUIElementAttribute, @@ -231,20 +229,18 @@ GetNativeFromGeckoAccessible(nsIAccessib if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) return [self position]; if ([attribute isEqualToString:NSAccessibilitySubroleAttribute]) return [self subrole]; if ([attribute isEqualToString:NSAccessibilityEnabledAttribute]) return [NSNumber numberWithBool:[self isEnabled]]; if ([attribute isEqualToString:NSAccessibilityValueAttribute]) return [self value]; -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) return NSAccessibilityRoleDescription([self role], nil); -#endif if ([attribute isEqualToString: (NSString*) kInstanceDescriptionAttribute]) return [self customDescription]; if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) return [NSNumber numberWithBool:[self isFocused]]; if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) return [self size]; if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) return [self window];
--- a/accessible/src/mac/mozActionElements.mm +++ b/accessible/src/mac/mozActionElements.mm @@ -221,34 +221,30 @@ enum CheckboxValue { } - (NSArray *)accessibilityActionNames { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; if ([self isEnabled]) { return [NSArray arrayWithObjects:NSAccessibilityPressAction, -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 NSAccessibilityShowMenuAction, -#endif nil]; } return nil; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (NSString *)accessibilityActionDescription:(NSString *)action { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 if ([action isEqualToString:NSAccessibilityShowMenuAction]) return @"show menu"; -#endif return [super accessibilityActionDescription:action]; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (void)accessibilityPerformAction:(NSString *)action { NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
--- a/accessible/src/mac/mozTextAccessible.mm +++ b/accessible/src/mac/mozTextAccessible.mm @@ -44,19 +44,17 @@ extern const NSString *kTopLevelUIElemen static NSArray *supportedAttributes = nil; if (!supportedAttributes) { // standard attributes that are shared and supported by all generic elements. supportedAttributes = [[NSArray alloc] initWithObjects:NSAccessibilityParentAttribute, // required NSAccessibilityRoleAttribute, // required NSAccessibilityTitleAttribute, NSAccessibilityValueAttribute, // required NSAccessibilitySubroleAttribute, -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 NSAccessibilityRoleDescriptionAttribute, -#endif NSAccessibilityPositionAttribute, // required NSAccessibilitySizeAttribute, // required NSAccessibilityWindowAttribute, // required NSAccessibilityFocusedAttribute, // required NSAccessibilityEnabledAttribute, // required kTopLevelUIElementAttribute, // required (on OS X 10.4+) kInstanceDescriptionAttribute, // required (on OS X 10.4+) /* text-specific attributes */ @@ -244,19 +242,17 @@ extern const NSString *kTopLevelUIElemen static NSArray *supportedAttributes = nil; if (!supportedAttributes) { // standard attributes that are shared and supported by all generic elements. supportedAttributes = [[NSArray alloc] initWithObjects:NSAccessibilityParentAttribute, // required NSAccessibilityRoleAttribute, // required NSAccessibilityTitleAttribute, NSAccessibilityValueAttribute, // required NSAccessibilityHelpAttribute, -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 NSAccessibilityRoleDescriptionAttribute, -#endif NSAccessibilityPositionAttribute, // required NSAccessibilitySizeAttribute, // required NSAccessibilityWindowAttribute, // required NSAccessibilityFocusedAttribute, // required NSAccessibilityEnabledAttribute, // required NSAccessibilityChildrenAttribute, // required NSAccessibilityHelpAttribute, // NSAccessibilityExpandedAttribute, // required @@ -276,52 +272,46 @@ extern const NSString *kTopLevelUIElemen } - (NSArray *)accessibilityActionNames { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; if ([self isEnabled]) { return [NSArray arrayWithObjects:NSAccessibilityConfirmAction, -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 NSAccessibilityShowMenuAction, -#endif nil]; } return nil; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (NSString *)accessibilityActionDescription:(NSString *)action { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 if ([action isEqualToString:NSAccessibilityShowMenuAction]) return @"show menu"; -#endif if ([action isEqualToString:NSAccessibilityConfirmAction]) return @"confirm"; return [super accessibilityActionDescription:action]; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (void)accessibilityPerformAction:(NSString *)action { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; // both the ShowMenu and Click action do the same thing. if ([self isEnabled]) { -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 if ([action isEqualToString:NSAccessibilityShowMenuAction]) [self showMenu]; -#endif if ([action isEqualToString:NSAccessibilityConfirmAction]) [self confirm]; } NS_OBJC_END_TRY_ABORT_BLOCK; } - (void)showMenu
--- a/accessible/src/msaa/nsAccessNodeWrap.cpp +++ b/accessible/src/msaa/nsAccessNodeWrap.cpp @@ -46,17 +46,17 @@ #include "nsCoreUtils.h" #include "nsRootAccessible.h" #include "nsWinUtils.h" #include "Statistics.h" #include "nsAttrName.h" #include "nsIDocument.h" #include "nsIDOMNodeList.h" -#include "nsIDOMNSHTMLElement.h" +#include "nsIDOMHTMLElement.h" #include "nsIFrame.h" #include "nsINameSpaceManager.h" #include "nsPIDOMWindow.h" #include "nsIServiceManager.h" #include "mozilla/Preferences.h" using namespace mozilla; @@ -543,22 +543,22 @@ nsAccessNodeWrap::get_childAt(unsigned a } STDMETHODIMP nsAccessNodeWrap::get_innerHTML(BSTR __RPC_FAR *aInnerHTML) { __try { *aInnerHTML = nsnull; - nsCOMPtr<nsIDOMNSHTMLElement> domNSElement(do_QueryInterface(GetNode())); - if (!domNSElement) + nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(GetNode()); + if (!htmlElement) return E_FAIL; // Node already shut down nsAutoString innerHTML; - domNSElement->GetInnerHTML(innerHTML); + htmlElement->GetInnerHTML(innerHTML); if (innerHTML.IsEmpty()) return S_FALSE; *aInnerHTML = ::SysAllocStringLen(innerHTML.get(), innerHTML.Length()); if (!*aInnerHTML) return E_OUTOFMEMORY; } __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
--- a/accessible/src/msaa/nsEventMap.h +++ b/accessible/src/msaa/nsEventMap.h @@ -85,17 +85,17 @@ static const PRUint32 gWinEventMap[] = { kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_MINIMIZE_START kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_MINIMIZE_END IA2_EVENT_DOCUMENT_LOAD_COMPLETE, // nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE IA2_EVENT_DOCUMENT_RELOAD, // nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD IA2_EVENT_DOCUMENT_LOAD_STOPPED, // nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED IA2_EVENT_DOCUMENT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_DOCUMENT_ATTRIBUTES_CHANGED IA2_EVENT_DOCUMENT_CONTENT_CHANGED, // nsIAccessibleEvent::EVENT_DOCUMENT_CONTENT_CHANGED kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_PROPERTY_CHANGED - kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_SELECTION_CHANGED + IA2_EVENT_PAGE_CHANGED, // nsIAccessibleEvent::IA2_EVENT_PAGE_CHANGED IA2_EVENT_TEXT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED IA2_EVENT_TEXT_CARET_MOVED, // nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED IA2_EVENT_TEXT_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_CHANGED IA2_EVENT_TEXT_INSERTED, // nsIAccessibleEvent::EVENT_TEXT_INSERTED IA2_EVENT_TEXT_REMOVED, // nsIAccessibleEvent::EVENT_TEXT_REMOVED IA2_EVENT_TEXT_UPDATED, // nsIAccessibleEvent::EVENT_TEXT_UPDATED IA2_EVENT_TEXT_SELECTION_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED IA2_EVENT_VISIBLE_DATA_CHANGED, // nsIAccessibleEvent::EVENT_VISIBLE_DATA_CHANGED @@ -126,12 +126,11 @@ static const PRUint32 gWinEventMap[] = { IA2_EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED, // nsIAccessibleEvent::EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED IA2_EVENT_HYPERLINK_SELECTED_LINK_CHANGED, // nsIAccessibleEvent::EVENT_HYPERLINK_SELECTED_LINK_CHANGED IA2_EVENT_HYPERTEXT_LINK_ACTIVATED, // nsIAccessibleEvent::EVENT_HYPERTEXT_LINK_ACTIVATED IA2_EVENT_HYPERTEXT_LINK_SELECTED, // nsIAccessibleEvent::EVENT_HYPERTEXT_LINK_SELECTED IA2_EVENT_HYPERLINK_START_INDEX_CHANGED, // nsIAccessibleEvent::EVENT_HYPERLINK_START_INDEX_CHANGED IA2_EVENT_HYPERTEXT_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_CHANGED IA2_EVENT_HYPERTEXT_NLINKS_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_NLINKS_CHANGED IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED - IA2_EVENT_PAGE_CHANGED, // nsIAccessibleEvent::EVENT_PAGE_CHANGED kEVENT_LAST_ENTRY // nsIAccessibleEvent::EVENT_LAST_ENTRY };
--- a/accessible/tests/mochitest/common.js +++ b/accessible/tests/mochitest/common.js @@ -37,17 +37,17 @@ const nsIAccessibleTableCell = Component const nsIAccessibleValue = Components.interfaces.nsIAccessibleValue; const nsIObserverService = Components.interfaces.nsIObserverService; const nsIDOMDocument = Components.interfaces.nsIDOMDocument; const nsIDOMEvent = Components.interfaces.nsIDOMEvent; const nsIDOMHTMLDocument = Components.interfaces.nsIDOMHTMLDocument; const nsIDOMNode = Components.interfaces.nsIDOMNode; -const nsIDOMNSHTMLElement = Components.interfaces.nsIDOMNSHTMLElement; +const nsIDOMHTMLElement = Components.interfaces.nsIDOMHTMLElement; const nsIDOMWindow = Components.interfaces.nsIDOMWindow; const nsIDOMXULElement = Components.interfaces.nsIDOMXULElement; const nsIPropertyElement = Components.interfaces.nsIPropertyElement; //////////////////////////////////////////////////////////////////////////////// // OS detect const MAC = (navigator.platform.indexOf("Mac") != -1)? true : false;
--- a/accessible/tests/mochitest/events.js +++ b/accessible/tests/mochitest/events.js @@ -10,17 +10,19 @@ const EVENT_FOCUS = nsIAccessibleEvent.E const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE; const EVENT_MENU_START = nsIAccessibleEvent.EVENT_MENU_START; const EVENT_MENU_END = nsIAccessibleEvent.EVENT_MENU_END; const EVENT_MENUPOPUP_START = nsIAccessibleEvent.EVENT_MENUPOPUP_START; const EVENT_MENUPOPUP_END = nsIAccessibleEvent.EVENT_MENUPOPUP_END; const EVENT_OBJECT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED; const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER; const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START; +const EVENT_SELECTION = nsIAccessibleEvent.EVENT_SELECTION; const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD; +const EVENT_SELECTION_REMOVE = nsIAccessibleEvent.EVENT_SELECTION_REMOVE; const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN; const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW; const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE; const EVENT_TEXT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED; const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED; const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED; const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED; const EVENT_TEXT_SELECTION_CHANGED = nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED; @@ -819,26 +821,26 @@ function synthClick(aNodeOrID, aCheckerO { var targetNode = this.DOMNode; if (targetNode instanceof nsIDOMDocument) { targetNode = this.DOMNode.body ? this.DOMNode.body : this.DOMNode.documentElement; } // Scroll the node into view, otherwise synth click may fail. - if (targetNode instanceof nsIDOMNSHTMLElement) { + if (targetNode instanceof nsIDOMHTMLElement) { targetNode.scrollIntoView(true); } else if (targetNode instanceof nsIDOMXULElement) { var targetAcc = getAccessible(targetNode); targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE); } var x = 1, y = 1; if (aArgs && ("where" in aArgs) && aArgs.where == "right") { - if (targetNode instanceof nsIDOMNSHTMLElement) + if (targetNode instanceof nsIDOMHTMLElement) x = targetNode.offsetWidth - 1; else if (targetNode instanceof nsIDOMXULElement) x = targetNode.boxObject.width - 1; } synthesizeMouse(targetNode, x, y, aArgs ? aArgs : {}); } this.finalCheck = function synthClick_finalCheck()
--- a/accessible/tests/mochitest/events/Makefile.in +++ b/accessible/tests/mochitest/events/Makefile.in @@ -77,17 +77,19 @@ include $(topsrcdir)/config/rules.mk test_focus_name.html \ test_focus_selects.html \ test_focus_tabbox.xul \ test_focus_tree.xul \ test_menu.xul \ test_mutation.html \ test_mutation.xhtml \ test_scroll.xul \ + test_selection_aria.html \ test_selection.html \ + test_selection.xul \ test_statechange.html \ test_text_alg.html \ test_text.html \ test_textattrchange.html \ test_tree.xul \ test_valuechange.html \ $(NULL)
--- a/accessible/tests/mochitest/events/test_selection.html +++ b/accessible/tests/mochitest/events/test_selection.html @@ -17,78 +17,97 @@ src="../events.js"></script> <script type="application/javascript" src="../states.js"></script> <script type="application/javascript"> //////////////////////////////////////////////////////////////////////////// // Invokers - function addSelection(aNode, aOption) - { - this.DOMNode = aNode; - this.optionNode = aOption; - - this.eventSeq = [ - new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.DOMNode)), - new invokerChecker(EVENT_SELECTION_ADD, getAccessible(this.optionNode)) - ]; - - this.invoke = function addselection_invoke() { - synthesizeMouse(this.optionNode, 1, 1, {}); - }; - - this.getID = function addselection_getID() { - return prettyName(this.optionNode) + " added to selection"; - }; - } - //////////////////////////////////////////////////////////////////////////// // Do tests - var gQueue = null; + //gA11yEventDumpToConsole = true; // debuggin - //var gA11yEventDumpID = "eventdump"; // debug stuff - + var gQueue = null; function doTests() { gQueue = new eventQueue(); - var select = document.getElementById("toppings"); - var option = document.getElementById("onions"); - gQueue.push(new addSelection(select, option)); + // open combobox + gQueue.push(new synthClick("combobox", + new invokerChecker(EVENT_FOCUS, "cb1_item1"))); + gQueue.push(new synthDownKey("cb1_item1", + new invokerChecker(EVENT_SELECTION, "cb1_item2"))); + + // closed combobox + gQueue.push(new synthEscapeKey("combobox", + new invokerChecker(EVENT_FOCUS, "combobox"))); + gQueue.push(new synthDownKey("cb1_item2", + new invokerChecker(EVENT_SELECTION, "cb1_item3"))); + + // listbox + gQueue.push(new synthClick("lb1_item1", + new invokerChecker(EVENT_SELECTION, "lb1_item1"))); + gQueue.push(new synthDownKey("lb1_item1", + new invokerChecker(EVENT_SELECTION, "lb1_item2"))); + + // multiselectable listbox + gQueue.push(new synthClick("lb2_item1", + new invokerChecker(EVENT_SELECTION, "lb2_item1"))); + gQueue.push(new synthDownKey("lb2_item1", + new invokerChecker(EVENT_SELECTION_ADD, "lb2_item2"), + { shiftKey: true })); + gQueue.push(new synthUpKey("lb2_item2", + new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item2"), + { shiftKey: true })); + gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true }, + new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item1"))); gQueue.invoke(); // Will call SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); addA11yLoadEvent(doTests); </script> </head> <body> <a target="_blank" - href="https://bugzilla.mozilla.org/show_bug.cgi?id=569653" - title="Make selection events async"> - Mozilla Bug 569653 + href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302" + title="Incorrect selection events in HTML, XUL and ARIA"> + Mozilla Bug 414302 </a> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"> </pre> - <p>Pizza</p> - <select id="toppings" name="toppings" multiple size=5> - <option value="mushrooms">mushrooms - <option value="greenpeppers">green peppers - <option value="onions" id="onions">onions - <option value="tomatoes">tomatoes - <option value="olives">olives + <select id="combobox"> + <option id="cb1_item1" value="mushrooms">mushrooms + <option id="cb1_item2" value="greenpeppers">green peppers + <option id="cb1_item3" value="onions" id="onions">onions + <option id="cb1_item4" value="tomatoes">tomatoes + <option id="cb1_item5" value="olives">olives </select> - <div id="testContainer"> - <iframe id="iframe"></iframe> - </div> + <select id="listbox" size=5> + <option id="lb1_item1" value="mushrooms">mushrooms + <option id="lb1_item2" value="greenpeppers">green peppers + <option id="lb1_item3" value="onions" id="onions">onions + <option id="lb1_item4" value="tomatoes">tomatoes + <option id="lb1_item5" value="olives">olives + </select> + + <p>Pizza</p> + <select id="listbox2" multiple size=5> + <option id="lb2_item1" value="mushrooms">mushrooms + <option id="lb2_item2" value="greenpeppers">green peppers + <option id="lb2_item3" value="onions" id="onions">onions + <option id="lb2_item4" value="tomatoes">tomatoes + <option id="lb2_item5" value="olives">olives + </select> + <div id="eventdump"></div> </body> </html>
new file mode 100644 --- /dev/null +++ b/accessible/tests/mochitest/events/test_selection.xul @@ -0,0 +1,244 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Selection event tests"> + + <script type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + function advanceTab(aTabsID, aDirection, aNextTabID) + { + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION, aNextTabID) + ]; + + this.invoke = function advanceTab_invoke() + { + getNode(aTabsID).advanceSelectedTab(aDirection, true); + } + + this.getID = function synthFocus_getID() + { + return "advanceTab on " + prettyName(aTabsID) + " to " + prettyName(aNextTabID); + } + } + + function select4FirstItems(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0)), + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(1)), + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(2)), + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(3)) + ]; + + this.invoke = function select4FirstItems_invoke() + { + synthesizeKey("VK_DOWN", { shiftKey: true }); // selects two items + synthesizeKey("VK_DOWN", { shiftKey: true }); + synthesizeKey("VK_DOWN", { shiftKey: true }); + } + + this.getID = function select4FirstItems_getID() + { + return "select 4 first items for " + prettyName(aID); + } + } + + function unselect4FirstItems(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(3)), + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(2)), + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(1)), + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0)) + ]; + + this.invoke = function unselect4FirstItems_invoke() + { + synthesizeKey("VK_UP", { shiftKey: true }); + synthesizeKey("VK_UP", { shiftKey: true }); + synthesizeKey("VK_UP", { shiftKey: true }); + synthesizeKey(" ", { ctrlKey: true }); // unselect first item + } + + this.getID = function unselect4FirstItems_getID() + { + return "unselect 4 first items for " + prettyName(aID); + } + } + + function selectAllItems(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode)) + ]; + + this.invoke = function selectAllItems_invoke() + { + synthesizeKey("VK_END", { shiftKey: true }); + } + + this.getID = function selectAllItems_getID() + { + return "select all items for " + prettyName(aID); + } + } + + function unselectAllItemsButFirst(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode)) + ]; + + this.invoke = function unselectAllItemsButFirst_invoke() + { + synthesizeKey("VK_HOME", { shiftKey: true }); + } + + this.getID = function unselectAllItemsButFirst_getID() + { + return "unselect all items for " + prettyName(aID); + } + } + + function unselectSelectItem(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0)), + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0)) + ]; + + this.invoke = function unselectSelectItem_invoke() + { + synthesizeKey(" ", { ctrlKey: true }); // select item + synthesizeKey(" ", { ctrlKey: true }); // unselect item + } + + this.getID = function unselectSelectItem_getID() + { + return "unselect and then select first item for " + prettyName(aID); + } + } + + /** + * Do tests. + */ + var gQueue = null; + + //gA11yEventDumpToConsole = true; // debuggin + + function doTests() + { + gQueue = new eventQueue(); + + ////////////////////////////////////////////////////////////////////////// + // tabbox + gQueue.push(new advanceTab("tabs", 1, "tab3")); + + ////////////////////////////////////////////////////////////////////////// + // listbox + gQueue.push(new synthClick("lb1_item1", + new invokerChecker(EVENT_SELECTION, "lb1_item1"))); + gQueue.push(new synthDownKey("lb1_item1", + new invokerChecker(EVENT_SELECTION, "lb1_item2"))); + + ////////////////////////////////////////////////////////////////////////// + // multiselectable listbox + gQueue.push(new synthClick("lb2_item1", + new invokerChecker(EVENT_SELECTION, "lb2_item1"))); + gQueue.push(new synthDownKey("lb2_item1", + new invokerChecker(EVENT_SELECTION_ADD, "lb2_item2"), + { shiftKey: true })); + gQueue.push(new synthUpKey("lb2_item2", + new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item2"), + { shiftKey: true })); + gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true }, + new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item1"))); + + ////////////////////////////////////////////////////////////////////////// + // selection event coalescence + + // fire 4 selection_add events + gQueue.push(new select4FirstItems("listbox2")); + // fire 4 selection_remove events + gQueue.push(new unselect4FirstItems("listbox2")); + // fire selection_within event + gQueue.push(new selectAllItems("listbox2")); + // fire selection_within event + gQueue.push(new unselectAllItemsButFirst("listbox2")); + // fire selection_remove/add events + gQueue.push(new unselectSelectItem("listbox2")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302" + title="Incorrect selection events in HTML, XUL and ARIA"> + Mozilla Bug 414302 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <tabbox id="tabbox" selectedIndex="1"> + <tabs id="tabs"> + <tab id="tab1" label="tab1"/> + <tab id="tab2" label="tab2"/> + <tab id="tab3" label="tab3"/> + <tab id="tab4" label="tab4"/> + </tabs> + <tabpanels> + <tabpanel><!-- tabpanel First elements go here --></tabpanel> + <tabpanel><button id="b1" label="b1"/></tabpanel> + <tabpanel><button id="b2" label="b2"/></tabpanel> + <tabpanel></tabpanel> + </tabpanels> + </tabbox> + + <listbox id="listbox"> + <listitem id="lb1_item1" label="item1"/> + <listitem id="lb1_item2" label="item2"/> + </listbox> + + <listbox id="listbox2" seltype="multiple"> + <listitem id="lb2_item1" label="item1"/> + <listitem id="lb2_item2" label="item2"/> + <listitem id="lb2_item3" label="item3"/> + <listitem id="lb2_item4" label="item4"/> + <listitem id="lb2_item5" label="item5"/> + <listitem id="lb2_item6" label="item6"/> + <listitem id="lb2_item7" label="item7"/> + </listbox> + + </hbox> +</window>
new file mode 100644 --- /dev/null +++ b/accessible/tests/mochitest/events/test_selection_aria.html @@ -0,0 +1,112 @@ +<html> + +<head> + <title>ARIA selection event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function selectTreeItem(aTreeID, aItemID) + { + this.treeNode = getNode(aTreeID); + this.itemNode = getNode(aItemID); + + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION, aItemID) + ]; + + this.invoke = function selectTreeItem_invoke() { + var itemNode = this.treeNode.querySelector("*[aria-selected='true']"); + if (itemNode) + itemNode.removeAttribute("aria-selected", "true"); + + this.itemNode.setAttribute("aria-selected", "true"); + } + + this.getID = function selectTreeItem_getID() + { + return "selectTreeItem " + prettyName(aItemID); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + + //var gA11yEventDumpID = "eventdump"; // debug stuff + + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new selectTreeItem("tree", "treeitem1")); + gQueue.push(new selectTreeItem("tree", "treeitem1a")); + gQueue.push(new selectTreeItem("tree", "treeitem1a1")); + + gQueue.push(new selectTreeItem("tree2", "tree2item1")); + gQueue.push(new selectTreeItem("tree2", "tree2item1a")); + gQueue.push(new selectTreeItem("tree2", "tree2item1a1")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=569653" + title="Make selection events async"> + Mozilla Bug 569653 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="tree" role="tree"> + <div id="treeitem1" role="treeitem">Canada + <div id="treeitem1a" role="treeitem">- Ontario + <div id="treeitem1a1" role="treeitem">-- Toronto</div> + </div> + <div id="treeitem1b" role="treeitem">- Manitoba</div> + </div> + <div id="treeitem2" role="treeitem">Germany</div> + <div id="treeitem3" role="treeitem">Russia</div> + </div> + + <div id="tree2" role="tree" aria-multiselectable="true"> + <div id="tree2item1" role="treeitem">Canada + <div id="tree2item1a" role="treeitem">- Ontario + <div id="tree2item1a1" role="treeitem">-- Toronto</div> + </div> + <div id="tree2item1b" role="treeitem">- Manitoba</div> + </div> + <div id="tree2item2" role="treeitem">Germany</div> + <div id="tree2item3" role="treeitem">Russia</div> + </div> + + <div id="eventdump"></div> +</body> +</html>
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -49,16 +49,20 @@ #endif pref("browser.chromeURL","chrome://browser/content/"); pref("browser.hiddenWindowChromeURL", "chrome://browser/content/hiddenWindow.xul"); // Enables some extra Extension System Logging (can reduce performance) pref("extensions.logging.enabled", false); +// Enables strict compatibility. To be toggled in bug 698653, to make addons +// compatibile by default. +pref("extensions.strictCompatibility", true); + // Preferences for AMO integration pref("extensions.getAddons.cache.enabled", true); pref("extensions.getAddons.maxResults", 15); pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%"); pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%"); pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%?src=firefox"); pref("extensions.webservice.discoverURL", "https://services.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%"); @@ -995,20 +999,24 @@ pref("services.sync.prefs.sync.spellchec pref("services.sync.prefs.sync.xpinstall.whitelist.required", true); #endif // Disable the error console pref("devtools.errorconsole.enabled", false); // Enable the Inspector pref("devtools.inspector.enabled", true); +pref("devtools.inspector.htmlHeight", 112); // Enable the style inspector pref("devtools.styleinspector.enabled", true); +// Enable the rules view +pref("devtools.ruleview.enabled", true); + // Enable the Scratchpad tool. pref("devtools.scratchpad.enabled", true); // Enable tools for Chrome development. pref("devtools.chrome.enabled", false); // Disable the GCLI enhanced command line. pref("devtools.gcli.enable", false); @@ -1051,8 +1059,11 @@ pref("devtools.editor.component", "orion // Whether the character encoding menu is under the main Firefox button. This // preference is a string so that localizers can alter it. pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties"); // Allow using tab-modal prompts when possible. pref("prompts.tab_modal.enabled", true); // Whether the Panorama should animate going in/out of tabs pref("browser.panorama.animate_zoom", true); + +// Enable the DOM full-screen API. +pref("full-screen-api.enabled", true);
--- a/browser/base/content/aboutDialog.js +++ b/browser/base/content/aboutDialog.js @@ -245,17 +245,17 @@ appUpdater.prototype = * @param aKeyPrefix * The prefix for the properties file entry to use for setting the * label and accesskey. */ setupUpdateButton: function(aKeyPrefix) { this.updateBtn.label = this.bundle.GetStringFromName(aKeyPrefix + ".label"); this.updateBtn.accessKey = this.bundle.GetStringFromName(aKeyPrefix + ".accesskey"); if (!document.commandDispatcher.focusedElement || - document.commandDispatcher.focusedElement.isSameNode(this.updateBtn)) + document.commandDispatcher.focusedElement == this.updateBtn) this.updateBtn.focus(); }, /** * Handles oncommand for the update button. */ buttonOnCommand: function() { if (this.isPending) {
--- a/browser/base/content/aboutHome.css +++ b/browser/base/content/aboutHome.css @@ -229,16 +229,20 @@ body[dir=rtl] #searchSubmit:active { 0 0 10px rgba(255,255,255,.5) inset, 0 0 0 1px rgba(0,0,0,.1), 0 2px 4px rgba(0,0,0,.2); color: rgb(60,60,60); font-size: .85em; cursor: pointer; } +#snippets:empty { + visibility: hidden; +} + @media all and (max-width: 470px) { #snippets { width: 65% } } @media all and (min-width: 470px) and (max-width: 850px) { #snippets { width: 45% } } @@ -360,13 +364,21 @@ body[dir=rtl] #restorePreviousSession::b position: absolute; color: rgb(150,150,150); font-size: .8em; width: 100%; text-align: center; bottom: 2%; } +#syncLinksContainer { + padding-top: 1em; +} + +.sync-link { + padding: 1em; +} + @media all and (max-height: 370px) { #bottomSection { visibility: hidden; } }
--- a/browser/base/content/aboutHome.xhtml +++ b/browser/base/content/aboutHome.xhtml @@ -97,11 +97,15 @@ <button id="restorePreviousSession">&historyRestoreLastSession.label;</button> </div> </div> <div id="bottomSection"> <div id="aboutMozilla"> <a href="http://www.mozilla.com/about/">&abouthome.aboutMozilla;</a> </div> + <div id="syncLinksContainer"> + <a href="javascript:void(0);" class="sync-link" id="setupSyncLink">&abouthome.syncSetup.label;</a> + <a href="javascript:void(0);" class="sync-link" id="pairDeviceLink">&abouthome.pairDevice.label;</a> + </div> </div> </body> </html>
--- a/browser/base/content/aboutSyncTabs.js +++ b/browser/base/content/aboutSyncTabs.js @@ -126,33 +126,47 @@ let RemoteTabViewer = { this._tabsList.clearSelection(); } }, bookmarkSingleTab: function() { let item = this._tabsList.selectedItems[0]; let uri = Weave.Utils.makeURI(item.getAttribute("url")); let title = item.getAttribute("title"); - PlacesUIUtils.showMinimalAddBookmarkUI(uri, title); + PlacesUIUtils.showBookmarkDialog({ action: "add" + , type: "bookmark" + , uri: uri + , title: title + , hiddenRows: [ "description" + , "location" + , "folderPicker" + , "loadInSidebar" + , "keyword" ] + }); }, bookmarkSelectedTabs: function() { let items = this._tabsList.selectedItems; let URIs = []; for (let i = 0;i < items.length;i++) { if (items[i].getAttribute("type") == "tab") { let uri = Weave.Utils.makeURI(items[i].getAttribute("url")); if (!uri) continue; URIs.push(uri); } } - if (URIs.length) - PlacesUIUtils.showMinimalAddMultiBookmarkUI(URIs); + if (URIs.length) { + PlacesUIUtils.showBookmarkDialog({ action: "add" + , type: "folder" + , URIList: URIs + , hiddenRows: [ "description" ] + }); + } }, _generateTabList: function() { let engine = Weave.Engines.get("tabs"); let list = this._tabsList; // clear out existing richlistitems let count = list.getRowCount();
--- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -373,21 +373,33 @@ var PlacesCommandHook = { * @param aURL (string) * the address of the link target * @param aTitle * The link text */ bookmarkLink: function PCH_bookmarkLink(aParent, aURL, aTitle) { var linkURI = makeURI(aURL); var itemId = PlacesUtils.getMostRecentBookmarkForURI(linkURI); - if (itemId == -1) - PlacesUIUtils.showMinimalAddBookmarkUI(linkURI, aTitle); + if (itemId == -1) { + PlacesUIUtils.showBookmarkDialog({ action: "add" + , type: "bookmark" + , uri: linkURI + , title: aTitle + , hiddenRows: [ "description" + , "location" + , "loadInSidebar" + , "folderPicker" + , "keyword" ] + }); + } else { - PlacesUIUtils.showItemProperties(itemId, - PlacesUtils.bookmarks.TYPE_BOOKMARK); + PlacesUIUtils.showBookmarkDialog({ action: "edit" + , type: "bookmark" + , itemId: itemId + }); } }, /** * List of nsIURI objects characterizing the tabs currently open in the * browser, modulo pinned tabs. The URIs will be in the order in which their * corresponding tabs appeared and duplicates are discarded. */ @@ -406,17 +418,21 @@ var PlacesCommandHook = { /** * Adds a folder with bookmarks to all of the currently open tabs in this * window. */ bookmarkCurrentPages: function PCH_bookmarkCurrentPages() { let pages = this.uniqueCurrentPages; if (pages.length > 1) { - PlacesUIUtils.showMinimalAddMultiBookmarkUI(pages); + PlacesUIUtils.showBookmarkDialog({ action: "add" + , type: "folder" + , URIList: pages + , hiddenRows: [ "description" ] + }); } }, /** * Updates disabled state for the "Bookmark All Tabs" command. */ updateBookmarkAllTabsCommand: function PCH_updateBookmarkAllTabsCommand() { @@ -446,20 +462,28 @@ var PlacesCommandHook = { var title = (arguments.length > 1) ? feedTitle : doc.title; var description; if (arguments.length > 2) description = feedSubtitle; else description = PlacesUIUtils.getDescriptionFromDocument(doc); - var toolbarIP = - new InsertionPoint(PlacesUtils.bookmarks.toolbarFolder, -1); - PlacesUIUtils.showMinimalAddLivemarkUI(feedURI, gBrowser.currentURI, - title, description, toolbarIP, true); + var toolbarIP = new InsertionPoint(PlacesUtils.toolbarFolderId, -1); + PlacesUIUtils.showBookmarkDialog({ action: "add" + , type: "livemark" + , feedURI: feedURI + , siteURI: gBrowser.currentURI + , title: title + , description: description + , defaultInsertionPoint: toolbarIP + , hiddenRows: [ "feedLocation" + , "siteLocation" + , "description" ] + }); }, /** * Opens the Places Organizer. * @param aLeftPaneRoot * The query to select in the organizer window - options * are: History, AllBookmarks, BookmarksMenu, BookmarksToolbar, * UnfiledBookmarks and Tags. @@ -850,28 +874,23 @@ var PlacesMenuDNDHandler = { * @param event * The DragEnter event that spawned the opening. */ onDragEnter: function PMDH_onDragEnter(event) { // Opening menus in a Places popup is handled by the view itself. if (!this._isStaticContainer(event.target)) return; - let ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId, - PlacesUtils.bookmarks.DEFAULT_INDEX, - Ci.nsITreeView.DROP_ON); - if (ip && PlacesControllerDragHelper.canDrop(ip, event.dataTransfer)) { - this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - this._loadTimer.initWithCallback(function() { - PlacesMenuDNDHandler._loadTimer = null; - event.target.lastChild.setAttribute("autoopened", "true"); - event.target.lastChild.showPopup(event.target.lastChild); - }, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT); - event.preventDefault(); - } + this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + this._loadTimer.initWithCallback(function() { + PlacesMenuDNDHandler._loadTimer = null; + event.target.lastChild.setAttribute("autoopened", "true"); + event.target.lastChild.showPopup(event.target.lastChild); + }, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT); + event.preventDefault(); event.stopPropagation(); }, /** * Handles dragexit on the <menu> element. * @returns true if the element is a container element (menu or * menu-toolbarbutton), false otherwise. */ @@ -947,17 +966,17 @@ var PlacesMenuDNDHandler = { }; var PlacesStarButton = { _hasBookmarksObserver: false, uninit: function PSB_uninit() { if (this._hasBookmarksObserver) { - PlacesUtils.bookmarks.removeObserver(this); + PlacesUtils.removeLazyBookmarkObserver(this); } if (this._pendingStmt) { this._pendingStmt.cancel(); delete this._pendingStmt; } }, QueryInterface: XPCOMUtils.generateQI([ @@ -1011,17 +1030,17 @@ var PlacesStarButton = { this._itemIds = this._itemIds.filter( function (id) aItemIds.indexOf(id) == -1 ).concat(aItemIds); this._updateStateInternal(); // Start observing bookmarks if needed. if (!this._hasBookmarksObserver) { try { - PlacesUtils.bookmarks.addObserver(this, false); + PlacesUtils.addLazyBookmarkObserver(this); this._hasBookmarksObserver = true; } catch(ex) { Components.utils.reportError("PlacesStarButton failed adding a bookmarks observer: " + ex); } } delete this._pendingStmt; }, this);
--- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -140,22 +140,18 @@ oncommand="PlacesCommandHook.showPlacesOrganizer('AllBookmarks');"/> <command id="Browser:ShowAllHistory" oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/> </commandset> <commandset id="inspectorCommands"> <command id="Inspector:Inspect" oncommand="InspectorUI.toggleInspection();"/> - <command id="Inspector:Previous" - oncommand="InspectorUI.inspectPrevious();" - disabled="true"/> - <command id="Inspector:Next" - oncommand="InspectorUI.inspectNext();" - disabled="true"/> + <command id="Inspector:Sidebar" + oncommand="InspectorUI.toggleSidebar();"/> </commandset> <broadcasterset id="mainBroadcasterSet"> <broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;" type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul" oncommand="toggleSidebar('viewBookmarksSidebar');"/> <!-- for both places and non-places, the sidebar lives at
--- a/browser/base/content/browser-syncui.js +++ b/browser/base/content/browser-syncui.js @@ -206,16 +206,34 @@ let gSyncUI = { }, onLoginFinish: function SUI_onLoginFinish() { // Clear out any login failure notifications let title = this._stringBundle.GetStringFromName("error.login.title"); this.clearError(title); }, + // Set visibility of "Setup Sync" link + showSetupSyncAboutHome: function SUI_showSetupSyncAboutHome(toShow) { + let browsers = gBrowser.browsers; + for (let i = 0; i < browsers.length; i++) { + let b = browsers[i]; + if ("about:home" == b.currentURI.spec) { + b.contentDocument.getElementById("setupSyncLink").hidden = !toShow; + } + } + }, + + onSetupComplete: function SUI_onSetupComplete() { + // Remove "setup sync" link in about:home if it is open. + this.showSetupSyncAboutHome(false); + + onLoginFinish(); + }, + onLoginError: function SUI_onLoginError() { // if login fails, any other notifications are essentially moot Weave.Notifications.removeAll(); // if we haven't set up the client, don't show errors if (this._needsSetup()) { this.updateUI(); return; @@ -250,16 +268,18 @@ let gSyncUI = { }, onLogout: function SUI_onLogout() { this.updateUI(); }, onStartOver: function SUI_onStartOver() { this.clearError(); + // Make "setup sync" link visible in about:home if it is open. + this.showSetupSyncAboutHome(true); }, onQuotaNotice: function onQuotaNotice(subject, data) { let title = this._stringBundle.GetStringFromName("warning.sync.quota.label"); let description = this._stringBundle.GetStringFromName("warning.sync.quota.description"); let buttons = []; buttons.push(new Weave.NotificationButton( this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.label"), @@ -286,26 +306,50 @@ let gSyncUI = { if (this._needsSetup()) this.openSetup(); else this.doSync(); }, //XXXzpao should be part of syncCommon.js - which we might want to make a module... // To be fixed in a followup (bug 583366) - openSetup: function SUI_openSetup() { + + /** + * Invoke the Sync setup wizard. + * + * @param wizardType + * Indicates type of wizard to launch: + * null -- regular set up wizard + * "pair" -- pair a device first + * "reset" -- reset sync + */ + + openSetup: function SUI_openSetup(wizardType) { let win = Services.wm.getMostRecentWindow("Weave:AccountSetup"); if (win) win.focus(); else { window.openDialog("chrome://browser/content/syncSetup.xul", - "weaveSetup", "centerscreen,chrome,resizable=no"); + "weaveSetup", "centerscreen,chrome,resizable=no", + wizardType); } }, + openAddDevice: function () { + if (!Weave.Utils.ensureMPUnlocked()) + return; + + let win = Services.wm.getMostRecentWindow("Sync:AddDevice"); + if (win) + win.focus(); + else + window.openDialog("chrome://browser/content/syncAddDevice.xul", + "syncAddDevice", "centerscreen,chrome,resizable=no"); + }, + openQuotaDialog: function SUI_openQuotaDialog() { let win = Services.wm.getMostRecentWindow("Sync:ViewQuota"); if (win) win.focus(); else Services.ww.activeWindow.openDialog( "chrome://browser/content/syncQuota.xul", "", "centerscreen,chrome,dialog,modal"); @@ -457,17 +501,17 @@ let gSyncUI = { break; case "weave:service:sync:delayed": this.onSyncDelay(); break; case "weave:service:quota:remaining": this.onQuotaNotice(); break; case "weave:service:setup-complete": - this.onLoginFinish(); + this.onSetupComplete(); break; case "weave:service:login:start": this.onActivityStart(); break; case "weave:service:login:finish": this.onLoginFinish(); break; case "weave:ui:login:error":
--- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -8,17 +8,16 @@ searchbar { tabbrowser { -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser"); } .tabbrowser-tabs { -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabs"); } -#tabbrowser-tabs[drag=detach][closebuttons=hidden] > .tabbrowser-arrowscrollbox > .tabs-newtab-button, #tabbrowser-tabs:not([overflow="true"]) + #new-tab-button, #tabbrowser-tabs[overflow="true"] > .tabbrowser-arrowscrollbox > .tabs-newtab-button, #TabsToolbar[currentset]:not([currentset*="tabbrowser-tabs,new-tab-button"]) > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button, #TabsToolbar[customizing="true"] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button { visibility: collapse; } .tabbrowser-tab { @@ -58,45 +57,16 @@ tabbrowser { -moz-transition: opacity 250ms; } .tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned] { position: fixed !important; display: block; /* position:fixed already does this (bug 579776), but let's be explicit */ } -.tabbrowser-tabs[drag] > .tabbrowser-tab[selected] { - z-index: 2; /* ensure selected tab stays on top despite -moz-transform */ -} - -.tabbrowser-tabs[drag] > .tabbrowser-tab[dragged] { - -moz-transition: 0s; /* suppress opening animation when reattaching tab */ -} - -/* visibility: collapse might collapse the tab bar, so we use this instead */ -.tabbrowser-tabs[drag=detach] > .tabbrowser-tab[dragged]:not(:only-child) { - min-width: 0 !important; - max-width: 0 !important; - border: 0 !important; - opacity: 0; - overflow: hidden; - -moz-transition: max-width 150ms ease-out; -} -.tabbrowser-tabs[drag=detach] > .tabbrowser-tab[dragged]:only-child { - visibility: hidden; -} - -.tabbrowser-tabs[drag=move] > .tabbrowser-tab[fadein]:not([dragged]) { - -moz-transition: -moz-transform 200ms ease-out; -} - -.tabbrowser-tabs[drag=finish] > .tabbrowser-tab[dragged][fadein] { - -moz-transition: -moz-transform 100ms ease-out; -} - #alltabs-popup { -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup"); } toolbar[printpreview="true"] { -moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar"); } @@ -382,16 +352,35 @@ window[chromehidden~="toolbar"] toolbar: /* Full Screen UI */ #fullscr-toggler { height: 1px; background: black; } +#full-screen-warning-container { + pointer-events: none; + position: fixed; + top: 0; + left: 0; + min-width: 100%; + min-height: 100%; +} + +#full-screen-warning-container[fade-warning-out] { + -moz-transition-property: opacity !important; + -moz-transition-duration: 500ms !important; + opacity: 0.0; +} + +#full-screen-warning-message { + pointer-events: auto; +} + #nav-bar[mode="text"] > #window-controls > toolbarbutton > .toolbarbutton-icon { display: -moz-box; } #nav-bar[mode="text"] > #window-controls > toolbarbutton > .toolbarbutton-text { display: none; }
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1341,17 +1341,16 @@ function BrowserStartup() { document.getElementById("status-bar").setAttribute("hideresizer", "true"); if (!window.toolbar.visible) { // adjust browser UI for popups if (gURLBar) { gURLBar.setAttribute("readonly", "true"); gURLBar.setAttribute("enablehistory", "false"); } - goSetCommandEnabled("Browser:OpenLocation", false); goSetCommandEnabled("cmd_newNavigatorTab", false); } #ifdef MENUBAR_CAN_AUTOHIDE updateAppButtonDisplay(); #endif CombinedStopReload.init(); @@ -1665,18 +1664,29 @@ function delayedStartup(isLoadingBlank, #else if (Win7Features) Win7Features.onOpenWindow(); #endif // called when we go into full screen, even if it is // initiated by a web page script window.addEventListener("fullscreen", onFullScreen, true); + + // Called when we enter DOM full-screen mode. Note we can already be in browser + // full-screen mode when we enter DOM full-screen mode. + window.addEventListener("mozfullscreenchange", onMozFullScreenChange, true); + + // When a restricted key is pressed in DOM full-screen mode, we should display + // the "Press ESC to exit" warning message. + window.addEventListener("MozShowFullScreenWarning", onShowFullScreenWarning, true); + if (window.fullScreen) onFullScreen(); + if (document.mozFullScreen) + onMozFullScreenChange(); #ifdef MOZ_SERVICES_SYNC // initialize the sync UI gSyncUI.init(); #endif TabView.init(); @@ -2120,17 +2130,17 @@ function loadOneOrMoreURIs(aURIString) try { gBrowser.loadTabs(aURIString.split("|"), false, true); } catch (e) { } } function focusAndSelectUrlBar() { - if (gURLBar && !gURLBar.readOnly) { + if (gURLBar) { if (window.fullScreen) FullScreen.mouseoverToggle(true); if (isElementVisible(gURLBar)) { gURLBar.focus(); gURLBar.select(); return true; } } @@ -2660,25 +2670,31 @@ function PageProxyClickHandler(aEvent) * to the DOM for unprivileged pages. */ function BrowserOnAboutPageLoad(document) { if (/^about:home$/i.test(document.documentURI)) { let ss = Components.classes["@mozilla.org/browser/sessionstore;1"]. getService(Components.interfaces.nsISessionStore); if (!ss.canRestoreLastSession) document.getElementById("sessionRestoreContainer").hidden = true; + // Sync-related links + if (Services.prefs.prefHasUserValue("services.sync.username")) { + document.getElementById("setupSyncLink").hidden = true; + } } } /** * Handle command events bubbling up from error page content */ function BrowserOnClick(event) { // Don't trust synthetic events - if (!event.isTrusted || event.target.localName != "button") + if (!event.isTrusted || + (event.target.localName != "button" && + event.target.className != "sync-link")) return; var ot = event.originalTarget; var errorDoc = ot.ownerDocument; // If the event came from an ssl error page, it is probably either the "Add // Exception…" or "Get me out of here!" button if (/^about:certerror/.test(errorDoc.documentURI)) { @@ -2798,16 +2814,26 @@ function BrowserOnClick(event) { else if (/^about:home$/i.test(errorDoc.documentURI)) { if (ot == errorDoc.getElementById("restorePreviousSession")) { let ss = Cc["@mozilla.org/browser/sessionstore;1"]. getService(Ci.nsISessionStore); if (ss.canRestoreLastSession) ss.restoreLastSession(); errorDoc.getElementById("sessionRestoreContainer").hidden = true; } + else if (ot == errorDoc.getElementById("pairDeviceLink")) { + if (Services.prefs.prefHasUserValue("services.sync.username")) { + gSyncUI.openAddDevice(); + } else { + gSyncUI.openSetup("pair"); + } + } + else if (ot == errorDoc.getElementById("setupSyncLink")) { + gSyncUI.openSetup(null); + } } } /** * Re-direct the browser to a known-safe page. This function is * used when, for example, the user browses to a known malware page * and is presented with about:blocked. The "Get me out of here!" * button should take the user to the default start page so that even @@ -2834,16 +2860,24 @@ function BrowserFullScreen() { window.fullScreen = !window.fullScreen; } function onFullScreen(event) { FullScreen.toggle(event); } +function onMozFullScreenChange(event) { + FullScreen.enterDomFullScreen(event); +} + +function onShowFullScreenWarning(event) { + FullScreen.showWarning(false); +} + function getWebNavigation() { try { return gBrowser.webNavigation; } catch (e) { return null; } } @@ -3117,17 +3151,26 @@ function openHomeDialog(aURL) } var bookmarksButtonObserver = { onDrop: function (aEvent) { let name = { }; let url = browserDragAndDrop.drop(aEvent, name); try { - PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(url), name); + PlacesUIUtils.showBookmarkDialog({ action: "add" + , type: "bookmark" + , uri: makeURI(url) + , title: name + , hiddenRows: [ "description" + , "location" + , "loadInSidebar" + , "folderPicker" + , "keyword" ] + }); } catch(ex) { } }, onDragOver: function (aEvent) { browserDragAndDrop.dragOver(aEvent); aEvent.dropEffect = "link"; }, @@ -3816,35 +3859,40 @@ var FullScreen = { // show/hide all menubars, toolbars (except the full screen toolbar) this.showXULChrome("toolbar", !enterFS); document.getElementById("View:FullScreen").setAttribute("checked", enterFS); if (enterFS) { // Add a tiny toolbar to receive mouseover and dragenter events, and provide affordance. // This will help simulate the "collapse" metaphor while also requiring less code and - // events than raw listening of mouse coords. - let fullScrToggler = document.getElementById("fullscr-toggler"); - if (!fullScrToggler) { - fullScrToggler = document.createElement("hbox"); - fullScrToggler.id = "fullscr-toggler"; - fullScrToggler.collapsed = true; - gNavToolbox.parentNode.insertBefore(fullScrToggler, gNavToolbox.nextSibling); + // events than raw listening of mouse coords. We don't add the toolbar in DOM full-screen + // mode, only browser full-screen mode. + if (!document.mozFullScreen) { + let fullScrToggler = document.getElementById("fullscr-toggler"); + if (!fullScrToggler) { + fullScrToggler = document.createElement("hbox"); + fullScrToggler.id = "fullscr-toggler"; + fullScrToggler.collapsed = true; + gNavToolbox.parentNode.insertBefore(fullScrToggler, gNavToolbox.nextSibling); + } + fullScrToggler.addEventListener("mouseover", this._expandCallback, false); + fullScrToggler.addEventListener("dragenter", this._expandCallback, false); } - fullScrToggler.addEventListener("mouseover", this._expandCallback, false); - fullScrToggler.addEventListener("dragenter", this._expandCallback, false); - if (gPrefService.getBoolPref("browser.fullscreen.autohide")) gBrowser.mPanelContainer.addEventListener("mousemove", this._collapseCallback, false); document.addEventListener("keypress", this._keyToggleCallback, false); document.addEventListener("popupshown", this._setPopupOpen, false); document.addEventListener("popuphidden", this._setPopupOpen, false); - this._shouldAnimate = true; + // We don't animate the toolbar collapse if in DOM full-screen mode, + // as the size of the content area would still be changing after the + // mozfullscreenchange event fired, which could confuse content script. + this._shouldAnimate = !document.mozFullScreen; this.mouseoverToggle(false); // Autohide prefs gPrefService.addObserver("browser.fullscreen", this, false); } else { // The user may quit fullscreen during an animation clearInterval(this._animationInterval); @@ -3855,30 +3903,97 @@ var FullScreen = { this._isAnimating = false; // This is needed if they use the context menu to quit fullscreen this._isPopupOpen = false; this.cleanup(); } }, + exitDomFullScreen : function(e) { + document.mozCancelFullScreen(); + }, + + enterDomFullScreen : function(event) { + if (!document.mozFullScreen) { + return; + } + + // We receive "mozfullscreenchange" events for each subdocument which + // is an ancestor of the document containing the element which requested + // full-screen. Only add listeners and show warning etc when the event we + // receive is targeted at the chrome document, i.e. only once every time + // we enter DOM full-screen mode. + let targetDoc = event.target.ownerDocument ? event.target.ownerDocument : event.target; + if (targetDoc != document) { + // However, if we receive a "mozfullscreenchange" event for a document + // which is not a subdocument of the currently selected tab, we know that + // we've switched tabs since the request to enter full-screen was made, + // so we should exit full-screen since the "full-screen document" isn't + // acutally visible. + if (targetDoc.defaultView.top != gBrowser.contentWindow) { + document.mozCancelFullScreen(); + } + return; + } + + let focusManger = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); + if (focusManger.activeWindow != window) { + // The top-level window has lost focus since the request to enter + // full-screen was made. Cancel full-screen. + document.mozCancelFullScreen(); + return; + } + + this.showWarning(true); + + // Exit DOM full-screen mode upon open, close, or change tab. + gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen); + gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen); + gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen); + + // Exit DOM full-screen mode when the browser window loses focus (ALT+TAB, etc). + window.addEventListener("deactivate", this.exitDomFullScreen, true); + + // Cancel any "hide the toolbar" animation which is in progress, and make + // the toolbar hide immediately. + clearInterval(this._animationInterval); + clearTimeout(this._animationTimeout); + this._isAnimating = false; + this._shouldAnimate = false; + this.mouseoverToggle(false); + + // If there's a full-screen toggler, remove its listeners, so that mouseover + // the top of the screen will not cause the toolbar to re-appear. + let fullScrToggler = document.getElementById("fullscr-toggler"); + if (fullScrToggler) { + fullScrToggler.removeEventListener("mouseover", this._expandCallback, false); + fullScrToggler.removeEventListener("dragenter", this._expandCallback, false); + } + }, + cleanup: function () { if (window.fullScreen) { gBrowser.mPanelContainer.removeEventListener("mousemove", this._collapseCallback, false); document.removeEventListener("keypress", this._keyToggleCallback, false); document.removeEventListener("popupshown", this._setPopupOpen, false); document.removeEventListener("popuphidden", this._setPopupOpen, false); gPrefService.removeObserver("browser.fullscreen", this); let fullScrToggler = document.getElementById("fullscr-toggler"); if (fullScrToggler) { fullScrToggler.removeEventListener("mouseover", this._expandCallback, false); fullScrToggler.removeEventListener("dragenter", this._expandCallback, false); } + this.cancelWarning(); + gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen); + gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen); + gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen); + window.removeEventListener("deactivate", this.exitDomFullScreen, true); } }, observe: function(aSubject, aTopic, aData) { if (aData == "browser.fullscreen.autohide") { if (gPrefService.getBoolPref("browser.fullscreen.autohide")) { gBrowser.mPanelContainer.addEventListener("mousemove", @@ -3989,16 +4104,94 @@ var FullScreen = { return; } gNavToolbox.style.marginTop = (animateFrameAmount * -1) + "px"; } FullScreen._animationInterval = setInterval(animateUpFrame, 70); }, + cancelWarning: function(event) { + if (!this.warningBox) { + return; + } + if (this.onWarningHidden) { + this.warningBox.removeEventListener("transitionend", this.onWarningHidden, false); + this.onWarningHidden = null; + } + if (this.warningFadeOutTimeout) { + clearTimeout(this.warningFadeOutTimeout); + this.warningFadeOutTimeout = null; + } + if (this.revealBrowserTimeout) { + clearTimeout(this.revealBrowserTimeout); + this.revealBrowserTimeout = null; + } + this.warningBox.removeAttribute("fade-warning-out"); + this.warningBox.removeAttribute("stop-obscuring-browser"); + this.warningBox.removeAttribute("obscure-browser"); + this.warningBox.setAttribute("hidden", true); + this.warningBox = null; + }, + + warningBox: null, + warningFadeOutTimeout: null, + revealBrowserTimeout: null, + onWarningHidden: null, + + // Fade in a warning that document has entered full-screen, and then fade it + // out after a few seconds. + showWarning: function(obscureBackground) { + if (!document.mozFullScreen || !gPrefService.getBoolPref("full-screen-api.warning.enabled")) { + return; + } + if (this.warningBox) { + // Warning is already showing. Reset the timer which fades out the warning message, + // and we'll restart the timer down below. + if (this.warningFadeOutTimeout) { + clearTimeout(this.warningFadeOutTimeout); + this.warningFadeOutTimeout = null; + } + } else { + this.warningBox = document.getElementById("full-screen-warning-container"); + // Add a listener to clean up state after the warning is hidden. + this.onWarningHidden = + function(event) { + if (event.propertyName != "opacity") + return; + this.cancelWarning(); + }.bind(this); + this.warningBox.addEventListener("transitionend", this.onWarningHidden, false); + this.warningBox.removeAttribute("hidden"); + } + + if (obscureBackground) { + // Partially obscure the <browser> element underneath the warning panel... + this.warningBox.setAttribute("obscure-browser", "true"); + // ...But set a timeout to stop obscuring the browser after a few moments. + this.warningBox.removeAttribute("stop-obscuring-browser"); + this.revealBrowserTimeout = + setTimeout( + function() { + if (this.warningBox) + this.warningBox.setAttribute("stop-obscuring-browser", "true"); + }.bind(this), + 1250); + } + + // Set a timeout to fade the warning out after a few moments. + this.warningFadeOutTimeout = + setTimeout( + function() { + if (this.warningBox) + this.warningBox.setAttribute("fade-warning-out", "true"); + }.bind(this), + 3000); + }, + mouseoverToggle: function(aShow, forceHide) { // Don't do anything if: // a) we're already in the state we want, // b) we're animating and will become collapsed soon, or // c) we can't collapse because it would be undesirable right now if (aShow != this._isChromeCollapsed || (!aShow && this._isAnimating) || (!aShow && !this._safeToCollapse(forceHide))) @@ -4028,17 +4221,20 @@ var FullScreen = { this._collapseCallback, false); } // Hiding/collapsing the toolbox interferes with the tab bar's scrollbox, // so we just move it off-screen instead. See bug 430687. gNavToolbox.style.marginTop = aShow ? "" : -gNavToolbox.getBoundingClientRect().height + "px"; - document.getElementById("fullscr-toggler").collapsed = aShow; + let toggler = document.getElementById("fullscr-toggler"); + if (toggler) { + toggler.collapsed = aShow; + } this._isChromeCollapsed = !aShow; if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 2) this._shouldAnimate = true; }, showXULChrome: function(aTag, aShow) { var els = document.getElementsByTagNameNS(this._XULNS, aTag); @@ -4154,17 +4350,17 @@ var XULBrowserWindow = { status: "", defaultStatus: "", jsStatus: "", jsDefaultStatus: "", overLink: "", startTime: 0, statusText: "", isBusy: false, - inContentWhitelist: ["about:addons", "about:permissions"], + inContentWhitelist: ["about:addons", "about:permissions", "about:sync-progress"], QueryInterface: function (aIID) { if (aIID.equals(Ci.nsIWebProgressListener) || aIID.equals(Ci.nsIWebProgressListener2) || aIID.equals(Ci.nsISupportsWeakReference) || aIID.equals(Ci.nsIXULBrowserWindow) || aIID.equals(Ci.nsISupports)) return this; @@ -5583,19 +5779,26 @@ function contentAreaClick(event, isPanel event.preventDefault(); return true; } if (linkNode.getAttribute("rel") == "sidebar") { // This is the Opera convention for a special link that, when clicked, // allows to add a sidebar panel. The link's title attribute contains // the title that should be used for the sidebar panel. - PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(href), - linkNode.getAttribute("title"), - null, null, true, true); + PlacesUIUtils.showBookmarkDialog({ action: "add" + , type: "bookmark" + , uri: makeURI(href) + , title: linkNode.getAttribute("title") + , loadBookmarkInSidebar: true + , hiddenRows: [ "description" + , "location" + , "folderPicker" + , "keyword" ] + }); event.preventDefault(); return true; } } handleLinkClick(event, href, linkNode); // Mark the page as a user followed link. This is done so that history can @@ -6621,18 +6824,28 @@ function AddKeywordForSearchField() { var postData; if (isURLEncoded) postData = formData.join("&"); else spec += "?" + formData.join("&"); - PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(spec), title, description, null, - null, null, "", postData, charset); + PlacesUIUtils.showBookmarkDialog({ action: "add" + , type: "bookmark" + , uri: makeURI(spec) + , title: title + , description: description + , keyword: "" + , postData: postData + , charSet: charset + , hiddenRows: [ "location" + , "loadInSidebar" + , "folderPicker" ] + }); } function SwitchDocumentDirection(aWindow) { aWindow.document.dir = (aWindow.document.dir == "ltr" ? "rtl" : "ltr"); for (var run = 0; run < aWindow.frames.length; run++) SwitchDocumentDirection(aWindow.frames[run]); } @@ -6674,24 +6887,16 @@ function getPluginInfo(pluginElement) var gPluginHandler = { get CrashSubmit() { delete this.CrashSubmit; Cu.import("resource://gre/modules/CrashSubmit.jsm", this); return this.CrashSubmit; }, - get crashReportHelpURL() { - delete this.crashReportHelpURL; - let url = formatURL("app.support.baseURL", true); - url += "plugin-crashed"; - this.crashReportHelpURL = url; - return this.crashReportHelpURL; - }, - // Map the plugin's name to a filtered version more suitable for user UI. makeNicePluginName : function (aName, aFilename) { if (aName == "Shockwave Flash") return "Adobe Flash"; // Clean up the plugin name by stripping off any trailing version numbers // or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar" let newName = aName.replace(/\bplug-?in\b/i, "").replace(/[\s\d\.\-\_\(\)]+$/, ""); @@ -6820,20 +7025,35 @@ var gPluginHandler = { {plugins: missingPluginsArray, browser: gBrowser.selectedBrowser}); }, // Callback for user clicking on a disabled plugin managePlugins: function (aEvent) { BrowserOpenAddonsMgr("addons://list/plugin"); }, - // Callback for user clicking "submit a report" link - submitReport : function(pluginDumpID, browserDumpID) { - // The crash reporter wants a DOM element it can append an IFRAME to, - // which it uses to submit a form. Let's just give it gBrowser. + // When user clicks try, checks if we should also send crash report in bg + retryPluginPage: function (browser, plugin, pluginDumpID, browserDumpID) { + let doc = plugin.ownerDocument; + + let statusDiv = + doc.getAnonymousElementByAttribute(plugin, "class", "submitStatus"); + let status = statusDiv.getAttribute("status"); + + let submitChk = + doc.getAnonymousElementByAttribute(plugin, "class", "pleaseSubmitCheckbox"); + + // Check status to make sure we haven't submitted already + if (status == "please" && submitChk.checked) { + this.submitReport(pluginDumpID, browserDumpID); + } + this.reloadPage(browser); + }, + + submitReport: function (pluginDumpID, browserDumpID) { this.CrashSubmit.submit(pluginDumpID); if (browserDumpID) this.CrashSubmit.submit(browserDumpID); }, // Callback for user clicking a "reload page" link reloadPage: function (browser) { browser.reload(); @@ -7041,18 +7261,17 @@ var gPluginHandler = { // Crashed-plugin event listener. Called for every instance of a // plugin in content. pluginInstanceCrashed: function (plugin, aEvent) { // Ensure the plugin and event are of the right type. if (!(aEvent instanceof Ci.nsIDOMDataContainerEvent)) return; let submittedReport = aEvent.getData("submittedCrashReport"); - let doPrompt = true; // XXX followup for .getData("doPrompt"); - let submitReports = true; // XXX followup for .getData("submitReports"); + let doPrompt = true; // XXX followup to get via gCrashReporter let pluginName = aEvent.getData("pluginName"); let pluginFilename = aEvent.getData("pluginFilename"); let pluginDumpID = aEvent.getData("pluginDumpID"); let browserDumpID = aEvent.getData("browserDumpID"); // Remap the plugin name to a more user-presentable form. pluginName = this.makeNicePluginName(pluginName, pluginFilename); @@ -7060,87 +7279,114 @@ var gPluginHandler = { // // Configure the crashed-plugin placeholder. // let doc = plugin.ownerDocument; let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox"); let statusDiv = doc.getAnonymousElementByAttribute(plugin, "class", "submitStatus"); #ifdef MOZ_CRASHREPORTER + let submitReports = gCrashReporter.submitReports; let status; // Determine which message to show regarding crash reports. if (submittedReport) { // submitReports && !doPrompt, handled in observer status = "submitted"; } else if (!submitReports && !doPrompt) { status = "noSubmit"; } else { // doPrompt + // link submit checkbox to gCrashReporter submitReports preference + let submitChk = doc.getAnonymousElementByAttribute( + plugin, "class", "pleaseSubmitCheckbox"); + submitChk.checked = submitReports; + submitChk.addEventListener("click", function() { + gCrashReporter.submitReports = this.checked; + }, false); + status = "please"; - // XXX can we make the link target actually be blank? - let pleaseLink = doc.getAnonymousElementByAttribute( - plugin, "class", "pleaseSubmitLink"); - this.addLinkClickCallback(pleaseLink, "submitReport", - pluginDumpID, browserDumpID); } // If we don't have a minidumpID, we can't (or didn't) submit anything. // This can happen if the plugin is killed from the task manager. if (!pluginDumpID) { - status = "noReport"; + status = "noReport"; } statusDiv.setAttribute("status", status); let bottomLinks = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgBottomLinks"); bottomLinks.style.display = "block"; let helpIcon = doc.getAnonymousElementByAttribute(plugin, "class", "helpIcon"); this.addLinkClickCallback(helpIcon, "openHelpPage"); - // If we're showing the link to manually trigger report submission, we'll - // want to be able to update all the instances of the UI for this crash to - // show an updated message when a report is submitted. + // If we're showing the checkbox to trigger report submission, we'll want + // to be able to update all the instances of the UI for this crash when + // one instance of the checkbox is modified or the status is updated. if (doPrompt) { + let submitReportsPrefObserver = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsISupportsWeakReference]), + observe : function(subject, topic, data) { + let submitChk = doc.getAnonymousElementByAttribute( + plugin, "class", "pleaseSubmitCheckbox"); + submitChk.checked = gCrashReporter.submitReports; + }, + handleEvent : function(event) { + // Not expected to be called, just here for the closure. + } + }; + let observer = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), observe : function(subject, topic, data) { let propertyBag = subject; if (!(propertyBag instanceof Ci.nsIPropertyBag2)) return; // Ignore notifications for other crashes. if (propertyBag.get("minidumpID") != pluginDumpID) return; statusDiv.setAttribute("status", data); }, handleEvent : function(event) { // Not expected to be called, just here for the closure. } - } + }; // Use a weak reference, so we don't have to remove it... Services.obs.addObserver(observer, "crash-report-status", true); + Services.obs.addObserver( + submitReportsPrefObserver, "submit-reports-pref-changed", true); + // ...alas, now we need something to hold a strong reference to prevent // it from being GC. But I don't want to manually manage the reference's // lifetime (which should be no greater than the page). - // Clever solution? Use a closue with an event listener on the document. + // Clever solution? Use a closure with an event listener on the document. // When the doc goes away, so do the listener references and the closure. doc.addEventListener("mozCleverClosureHack", observer, false); + doc.addEventListener( + "mozCleverClosureHack", submitReportsPrefObserver, false); } #endif let crashText = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgCrashed"); crashText.textContent = messageString; let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); let link = doc.getAnonymousElementByAttribute(plugin, "class", "reloadLink"); +#ifdef MOZ_CRASHREPORTER + this.addLinkClickCallback( + link, "retryPluginPage", browser, plugin, pluginDumpID, browserDumpID); +#else this.addLinkClickCallback(link, "reloadPage", browser); +#endif let notificationBox = gBrowser.getNotificationBox(browser); // Is the <object>'s size too small to hold what we want to show? if (this.isTooSmall(plugin, overlay)) { // Hide the overlay's contents. Use visibility style, so that it // doesn't collapse down to 0x0. overlay.style.visibility = "hidden"; @@ -7196,17 +7442,20 @@ var gPluginHandler = { let notification = notificationBox.appendNotification(messageString, "plugin-crashed", iconURL, priority, buttons); // Add the "learn more" link. let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; let link = notification.ownerDocument.createElementNS(XULNS, "label"); link.className = "text-link"; link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore")); - link.href = gPluginHandler.crashReportHelpURL; + let crashurl = formatURL("app.support.baseURL", true); + crashurl += "plugin-crashed-notificationbar"; + link.href = crashurl; + let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText"); description.appendChild(link); // Remove the notfication when the page is reloaded. doc.defaultView.top.addEventListener("unload", function() { notificationBox.removeNotification(notification); }, false); } @@ -7958,17 +8207,17 @@ let DownloadMonitorPanel = { return; } // Find the download with the longest remaining time let numPaused = 0; let maxTime = -Infinity; let dls = gDownloadMgr.activeDownloads; while (dls.hasMoreElements()) { - let dl = dls.getNext().QueryInterface(Ci.nsIDownload); + let dl = dls.getNext(); if (dl.state == gDownloadMgr.DOWNLOAD_DOWNLOADING) { // Figure out if this download takes longer if (dl.speed > 0 && dl.size > 0) maxTime = Math.max(maxTime, (dl.size - dl.amountTransferred) / dl.speed); else maxTime = -1; } else if (dl.state == gDownloadMgr.DOWNLOAD_PAUSED) @@ -8705,24 +8954,26 @@ function toggleAddonBar() { let addonBar = document.getElementById("addon-bar"); setToolbarVisibility(addonBar, addonBar.collapsed); } var Scratchpad = { prefEnabledName: "devtools.scratchpad.enabled", openScratchpad: function SP_openScratchpad() { - const SCRATCHPAD_WINDOW_URL = "chrome://browser/content/scratchpad.xul"; - const SCRATCHPAD_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no"; - - return Services.ww.openWindow(null, SCRATCHPAD_WINDOW_URL, "_blank", - SCRATCHPAD_WINDOW_FEATURES, null); - }, + return this.ScratchpadManager.openScratchpad(); + } }; +XPCOMUtils.defineLazyGetter(Scratchpad, "ScratchpadManager", function() { + let tmp = {}; + Cu.import("resource:///modules/devtools/scratchpad-manager.jsm", tmp); + return tmp.ScratchpadManager; +}); + XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () { #ifdef XP_WIN // Only show resizers on Windows 2000 and XP let sysInfo = Components.classes["@mozilla.org/system-info;1"] .getService(Components.interfaces.nsIPropertyBag2); return parseFloat(sysInfo.getProperty("version")) < 6; #else
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -961,31 +961,73 @@ <tabbrowser id="content" disablehistory="true" flex="1" contenttooltip="aHTMLTooltip" tabcontainer="tabbrowser-tabs" contentcontextmenu="contentAreaContextMenu" autocompletepopup="PopupAutoComplete" onclick="return contentAreaClick(event, false);"/> <statuspanel id="statusbar-display" inactive="true"/> </vbox> + <splitter id="devtools-side-splitter" hidden="true"/> + <vbox id="devtools-sidebar-box" hidden="true" + style="min-width: 18em; width: 22em; max-width: 42em;" persist="width"> + <toolbar id="devtools-sidebar-toolbar" nowindowdrag="true"/> + <deck id="devtools-sidebar-deck" flex="1"/> + </vbox> <vbox id="browser-border-end" hidden="true" layer="true"/> </hbox> + <hbox id="full-screen-warning-container" hidden="true" fadeout="true"> + <hbox style="min-width: 100%;" pack="center"> <!-- Inner hbox needed due to bug 579776. --> + <hbox id="full-screen-warning-message"> + <description id="full-screen-warning-text" value="&domFullScreenWarning.label;"></description> + </hbox> + </hbox> + </hbox> + <vbox id="browser-bottombox" layer="true"> <toolbar id="inspector-toolbar" nowindowdrag="true" hidden="true"> - <toolbarbutton id="inspector-inspect-toolbutton" - label="&inspectButton.label;" - accesskey="&inspectButton.accesskey;" - command="Inspector:Inspect"/> - <toolbarseparator /> - <hbox id="inspector-tools"> - <!-- registered tools go here --> - </hbox> + <vbox flex="1"> + <resizer id="inspector-top-resizer" flex="1" + class="inspector-resizer" + dir="top" disabled="true" + element="inspector-tree-box"/> + <hbox> +#ifdef XP_MACOSX + <toolbarbutton id="highlighter-closebutton" + oncommand="InspectorUI.closeInspectorUI(false);" + tooltiptext="&inspectCloseButton.tooltiptext;"/> +#endif + <toolbarbutton id="inspector-inspect-toolbutton" + label="&inspectButton.label;" + accesskey="&inspectButton.accesskey;" + command="Inspector:Inspect"/> + <arrowscrollbox id="inspector-breadcrumbs" + flex="1" orient="horizontal" + clicktoscroll="true"/> + <hbox id="inspector-tools"> + <toolbarbutton id="inspector-style-button" + label="&inspectStyleButton.label;" + accesskey="&inspectStyleButton.accesskey;" + command="Inspector:Sidebar"/> + <!-- registered tools go here --> + </hbox> +#ifndef XP_MACOSX + <toolbarbutton id="highlighter-closebutton" + oncommand="InspectorUI.closeInspectorUI(false);" + tooltiptext="&inspectCloseButton.tooltiptext;"/> +#endif + <resizer id="inspector-end-resizer" + class="inspector-resizer" + dir="top" disabled="true" + element="inspector-tree-box"/> + </hbox> + </vbox> </toolbar> <toolbar id="addon-bar" toolbarname="&addonBarCmd.label;" accesskey="&addonBarCmd.accesskey;" collapsed="true" class="toolbar-primary chromeclass-toolbar" context="toolbar-context-menu" toolboxid="navigator-toolbox" mode="icons" iconsize="small" defaulticonsize="small" lockiconsize="true" @@ -1016,16 +1058,20 @@ </svg:svg> #endif #ifdef XP_MACOSX <svg:svg height="0"> <svg:mask id="pinstripe-keyhole-forward-mask" maskContentUnits="objectBoundingBox"> <svg:rect x="0" y="0" width="1" height="1" fill="white"/> <svg:circle cx="-0.41" cy="0.5" r="0.65"/> </svg:mask> + <svg:mask id="pinstripe-urlbar-back-button-mask" maskContentUnits="userSpaceOnUse"> + <svg:rect x="0" y="-5" width="10000" height="55" fill="white"/> + <svg:circle cx="-9" cy="11" r="15"/> + </svg:mask> <svg:mask id="pinstripe-tab-ontop-left-curve-mask" maskContentUnits="userSpaceOnUse"> <svg:circle cx="9" cy="3" r="3" fill="white"/> <svg:rect x="9" y="0" width="3" height="3" fill="white"/> <svg:rect x="6" y="3" width="6" height="19" fill="white"/> <svg:rect x="1" y="17" width="5" height="5" fill="white"/> <svg:circle cx="1" cy="17" r="5"/> <svg:rect x="0" y="22" width="12" height="1" fill="white"/> </svg:mask>
--- a/browser/base/content/highlighter.css +++ b/browser/base/content/highlighter.css @@ -25,20 +25,27 @@ #highlighter-veil-rightbox { -moz-box-flex: 1; } #highlighter-veil-middlebox:-moz-locale-dir(rtl) { -moz-box-direction: reverse; } -#highlighter-close-button { - position: absolute; - pointer-events: auto; - z-index: 1; +.inspector-breadcrumbs-button { + direction: ltr; +} + +.inspector-resizer { + display: none; +} + +#inspector-toolbar[treepanel-open] > vbox > #inspector-top-resizer, +#inspector-toolbar[treepanel-open] > vbox > hbox > #inspector-end-resizer { + display: -moz-box; } /* * Node Infobar */ #highlighter-nodeinfobar-container { position: absolute;
--- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -272,19 +272,20 @@ nsContextMenu.prototype = { } // Reload image depends on an image that's not fully loaded this.showItem("context-reloadimage", (this.onImage && !this.onCompletedImage)); // View image depends on having an image that's not standalone // (or is in a frame), or a canvas. this.showItem("context-viewimage", (this.onImage && - (!this.onStandaloneImage || this.inFrame)) || this.onCanvas); + (!this.inSyntheticDoc || this.inFrame)) || this.onCanvas); - this.showItem("context-viewvideo", this.onVideo); + // View video depends on not having a standalone video. + this.showItem("context-viewvideo", this.onVideo && (!this.inSyntheticDoc || this.inFrame)); this.setItemAttr("context-viewvideo", "disabled", !this.mediaURL); // View background image depends on whether there is one. this.showItem("context-viewbgimage", shouldShow && !this._hasMultipleBGImages); this.showItem("context-sep-viewbgimage", shouldShow && !this._hasMultipleBGImages); document.getElementById("context-viewbgimage") .disabled = !this.hasBGImage; @@ -461,32 +462,32 @@ nsContextMenu.prototype = { this.shouldDisplay = false; return; } // Initialize contextual info. this.onImage = false; this.onLoadedImage = false; this.onCompletedImage = false; - this.onStandaloneImage = false; this.onCanvas = false; this.onVideo = false; this.onAudio = false; this.onTextInput = false; this.onKeywordField = false; this.mediaURL = ""; this.onLink = false; this.onMailtoLink = false; this.onSaveableLink = false; this.link = null; this.linkURL = ""; this.linkURI = null; this.linkProtocol = ""; this.onMathML = false; this.inFrame = false; + this.inSyntheticDoc = false; this.hasBGImage = false; this.bgImageURL = ""; this.onEditableArea = false; this.isDesignMode = false; // Clear any old spellchecking items from the menu, this used to // be in the menu hiding code but wasn't getting called in all // situations. Here, we can ensure it gets cleaned up any time the @@ -495,33 +496,33 @@ nsContextMenu.prototype = { InlineSpellCheckerUI.clearSuggestionsFromMenu(); InlineSpellCheckerUI.clearDictionaryListFromMenu(); InlineSpellCheckerUI.uninit(); // Remember the node that was clicked. this.target = aNode; + // Check if we are in a synthetic document (stand alone image, video, etc.). + this.inSyntheticDoc = this.target.ownerDocument.mozSyntheticDocument; // First, do checks for nodes that never have children. if (this.target.nodeType == Node.ELEMENT_NODE) { // See if the user clicked on an image. if (this.target instanceof Ci.nsIImageLoadingContent && this.target.currentURI) { this.onImage = true; var request = this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST); if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE)) this.onLoadedImage = true; if (request && (request.imageStatus & request.STATUS_LOAD_COMPLETE)) this.onCompletedImage = true; this.mediaURL = this.target.currentURI.spec; - if (this.target.ownerDocument instanceof ImageDocument) - this.onStandaloneImage = true; } else if (this.target instanceof HTMLCanvasElement) { this.onCanvas = true; } else if (this.target instanceof HTMLVideoElement) { this.onVideo = true; this.mediaURL = this.target.currentSrc || this.target.src; } @@ -1392,21 +1393,34 @@ nsContextMenu.prototype = { addBookmarkForFrame: function CM_addBookmarkForFrame() { var doc = this.target.ownerDocument; var uri = doc.documentURIObject; var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri); if (itemId == -1) { var title = doc.title; var description = PlacesUIUtils.getDescriptionFromDocument(doc); - PlacesUIUtils.showMinimalAddBookmarkUI(uri, title, description); + PlacesUIUtils.showBookmarkDialog({ action: "add" + , type: "bookmark" + , uri: uri + , title: title + , description: description + , hiddenRows: [ "description" + , "location" + , "loadInSidebar" + , "folderPicker" + , "keyword" ] + }); } - else - PlacesUIUtils.showItemProperties(itemId, - PlacesUtils.bookmarks.TYPE_BOOKMARK); + else { + PlacesUIUtils.showBookmarkDialog({ action: "edit" + , type: "bookmark" + , itemId: itemId + }); + } }, savePageAs: function CM_savePageAs() { saveDocument(this.browser.contentDocument); }, sendPage: function CM_sendPage() { MailIntegration.sendLinkForWindow(this.browser.contentWindow); @@ -1438,23 +1452,23 @@ nsContextMenu.prototype = { break; case "hidecontrols": media.removeAttribute("controls"); break; case "showcontrols": media.setAttribute("controls", "true"); break; case "showstats": - var event = document.createEvent("CustomEvent"); - event.initCustomEvent("media-showStatistics", false, true, true); + var event = document.createEvent("CustomEvent"); + event.initCustomEvent("media-showStatistics", false, true, true); media.dispatchEvent(event); break; case "hidestats": - var event = document.createEvent("CustomEvent"); - event.initCustomEvent("media-showStatistics", false, true, false); + var event = document.createEvent("CustomEvent"); + event.initCustomEvent("media-showStatistics", false, true, false); media.dispatchEvent(event); break; } }, copyMediaLocation : function () { var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]. getService(Ci.nsIClipboardHelper);
new file mode 100644 --- /dev/null +++ b/browser/base/content/syncProgress.js @@ -0,0 +1,105 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Firefox Sync. + * + * The Initial Developer of the Original Code is the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Allison Naaktgeboren <ally@mozilla.com> (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://services-sync/main.js"); + +let gProgressBar; +let gCounter = 0; + +function onLoad(event) { + Services.obs.addObserver(onEngineSync, "weave:engine:sync:finish", false); + Services.obs.addObserver(onEngineSync, "weave:engine:sync:error", false); + Services.obs.addObserver(onServiceSync, "weave:service:sync:finish", false); + Services.obs.addObserver(onServiceSync, "weave:service:sync:error", false); + + gProgressBar = document.getElementById('uploadProgressBar'); + + if (Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) { + gProgressBar.style.display = "inline"; + } + else { + gProgressBar.style.display = "none"; + } +} + +function onUnload(event) { + cleanUpObservers(); +} + +function cleanUpObservers() { + try { + Services.obs.removeObserver(onEngineSync, "weave:engine:sync:finish", false); + Services.obs.removeObserver(onEngineSync, "weave:engine:sync:error", false); + Services.obs.removeObserver(onServiceSync, "weave:service:sync:finish", false); + Services.obs.removeObserver(onServiceSync, "weave:service:sync:error", false); + } + catch (e) { + // may be double called by unload & exit. Ignore. + } +} + +function onEngineSync(subject, topic, data) { + // The Clients engine syncs first. At this point we don't necessarily know + // yet how many engines will be enabled, so we'll ignore the Clients engine + // and evaluate how many engines are enabled when the first "real" engine + // syncs. + if (data == "clients") { + return; + } + + if (!gCounter && + Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) { + gProgressBar.max = Weave.Engines.getEnabled().length; + } + + gCounter += 1; + gProgressBar.setAttribute("value", gCounter); +} + +function onServiceSync(subject, topic, data) { + // To address the case where 0 engines are synced, we will fill the + // progress bar so the user knows that the sync has finished. + gProgressBar.setAttribute("value", gProgressBar.max); + cleanUpObservers(); +} + +function closeTab() { + window.close(); +}
new file mode 100644 --- /dev/null +++ b/browser/base/content/syncProgress.xhtml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License +# Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" +# basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is Firefox Sync. +# +# The Initial Developer of the Original Code is the Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2011 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Allison Naaktgeboren <ally@mozilla.com> (original author) +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +<!DOCTYPE html [ + <!ENTITY % htmlDTD + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "DTD/xhtml1-strict.dtd"> + %htmlDTD; + <!ENTITY % syncProgressDTD + SYSTEM "chrome://browser/locale/syncProgress.dtd"> + %syncProgressDTD; + <!ENTITY % syncSetupDTD + SYSTEM "chrome://browser/locale/syncSetup.dtd"> + %syncSetupDTD; +]> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>&syncProgress.pageTitle;</title> + + <link rel="stylesheet" type="text/css" media="all" + href="chrome://browser/skin/syncProgress.css"/> + + <link rel="icon" type="image/png" id="favicon" + href="chrome://browser/skin/sync-16.png"/> + + <script type="text/javascript;version=1.8" + src="chrome://browser/content/syncProgress.js"/> + </head> + <body onload="onLoad(event)" onunload="onUnload(event)"> + <title>&setup.successPage.title;</title> + <div id="floatingBox" class="main-content"> + <div id="title"> + <h1>&setup.successPage.title;</h1> + </div> + <div id="successLogo"> + <img id="brandSyncLogo" src="chrome://browser/skin/sync-128.png" alt="&syncProgress.logoAltText;" /> + </div> + <div id="loadingText"> + <p id="blurb">&syncProgress.textBlurb; </p> + </div> + <div id="progressBar"> + <progress id="uploadProgressBar" value="0"/> + </div> + <div id="bottomRow"> + <button id="closeButton" onclick="closeTab()">&syncProgress.closeButton; </button> + </div> + </div> + </body> +</html>
--- a/browser/base/content/syncSetup.js +++ b/browser/base/content/syncSetup.js @@ -19,16 +19,17 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): * Edward Lee <edilee@mozilla.com> * Mike Connor <mconnor@mozilla.com> * Philipp von Weitershausen <philipp@weitershausen.de> * Paul O’Shannessy <paul@oshannessy.com> * Richard Newman <rnewman@mozilla.com> + * Allison Naaktgeboren <ally@mozilla.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your @@ -48,17 +49,16 @@ const Cu = Components.utils; const PAIR_PAGE = 0; const INTRO_PAGE = 1; const NEW_ACCOUNT_START_PAGE = 2; const EXISTING_ACCOUNT_CONNECT_PAGE = 3; const EXISTING_ACCOUNT_LOGIN_PAGE = 4; const OPTIONS_PAGE = 5; const OPTIONS_CONFIRM_PAGE = 6; -const SETUP_SUCCESS_PAGE = 7; // Broader than we'd like, but after this changed from api-secure.recaptcha.net // we had no choice. At least we only do this for the duration of setup. // See discussion in Bugs 508112 and 653307. const RECAPTCHA_DOMAIN = "https://www.google.com"; const PIN_PART_LENGTH = 4; @@ -406,42 +406,31 @@ var gSyncSetup = { this.wizard.getButton("extra1").hidden = false; this.wizard.getButton("next").hidden = false; this.wizard.getButton("back").hidden = false; this.onServerCommand(); this.wizard.canRewind = true; this.checkFields(); break; case EXISTING_ACCOUNT_CONNECT_PAGE: + Weave.Svc.Prefs.set("firstSync", "existingAccount"); this.wizard.getButton("next").hidden = false; this.wizard.getButton("back").hidden = false; this.wizard.getButton("extra1").hidden = false; this.wizard.canAdvance = false; this.wizard.canRewind = true; this.startEasySetup(); break; case EXISTING_ACCOUNT_LOGIN_PAGE: this.wizard.getButton("next").hidden = false; this.wizard.getButton("back").hidden = false; this.wizard.getButton("extra1").hidden = false; this.wizard.canRewind = true; this.checkFields(); break; - case SETUP_SUCCESS_PAGE: - this.wizard.canRewind = false; - this.wizard.canAdvance = true; - this.wizard.getButton("back").hidden = true; - this.wizard.getButton("next").hidden = true; - this.wizard.getButton("cancel").hidden = true; - this.wizard.getButton("finish").hidden = false; - this._handleSuccess(); - if (this.wizardType == "pair") { - this.completePairing(); - } - break; case OPTIONS_PAGE: this.wizard.canRewind = false; this.wizard.canAdvance = true; if (!this._resettingSync) { this.wizard.getButton("next").label = this._stringBundle.GetStringFromName("button.syncOptionsDone.label"); this.wizard.getButton("next").removeAttribute("accesskey"); } @@ -468,17 +457,17 @@ var gSyncSetup = { onWizardAdvance: function () { // Check pageIndex so we don't prompt before the Sync setup wizard appears. // This is a fallback in case the Master Password gets locked mid-wizard. if ((this.wizard.pageIndex >= 0) && !Weave.Utils.ensureMPUnlocked()) { return false; } - + switch (this.wizard.pageIndex) { case PAIR_PAGE: this.startPairing(); return false; case NEW_ACCOUNT_START_PAGE: // If the user selects Next (e.g. by hitting enter) when we haven't // executed the delayed checks yet, execute them immediately. if (this._checkAccountTimer) { @@ -514,43 +503,44 @@ var gSyncSetup = { let error = Weave.Service.createAccount(email, password, challenge, response); if (error == null) { Weave.Service.account = email; Weave.Service.password = password; Weave.Service.passphrase = Weave.Utils.generatePassphrase(); this._handleNoScript(false); - this.wizard.pageIndex = SETUP_SUCCESS_PAGE; + Weave.Svc.Prefs.set("firstSync", "newAccount"); + this.wizardFinish(); return false; } image.setAttribute("status", "error"); label.value = Weave.Utils.getErrorString(error); return false; case EXISTING_ACCOUNT_LOGIN_PAGE: Weave.Service.account = Weave.Utils.normalizeAccount( document.getElementById("existingAccountName").value); Weave.Service.password = document.getElementById("existingPassword").value; let pp = document.getElementById("existingPassphrase").value; Weave.Service.passphrase = Weave.Utils.normalizePassphrase(pp); - if (Weave.Service.login()) - this.wizard.pageIndex = SETUP_SUCCESS_PAGE; + if (Weave.Service.login()) { + this.wizardFinish(); + } return false; case OPTIONS_PAGE: let desc = document.getElementById("mergeChoiceRadio").selectedIndex; // No confirmation needed on new account setup or merge option // with existing account. if (this._settingUpNew || (!this._resettingSync && desc == 0)) return this.returnFromOptions(); return this._handleChoice(); case OPTIONS_CONFIRM_PAGE: if (this._resettingSync) { - this.onWizardFinish(); - window.close(); + this.wizardFinish(); return false; } return this.returnFromOptions(); } return true; }, onWizardBack: function () { @@ -575,50 +565,49 @@ var gSyncSetup = { case OPTIONS_CONFIRM_PAGE: // Backing up from the confirmation page = resetting first sync to merge. document.getElementById("mergeChoiceRadio").selectedIndex = 0; return this.returnFromOptions(); } return true; }, - onWizardFinish: function () { + wizardFinish: function () { this.setupInitialSync(); + if (this.wizardType == "pair") { + this.completePairing(); + } + if (!this._resettingSync) { function isChecked(element) { return document.getElementById(element).hasAttribute("checked"); } let prefs = ["engine.bookmarks", "engine.passwords", "engine.history", "engine.tabs", "engine.prefs"]; for (let i = 0;i < prefs.length;i++) { Weave.Svc.Prefs.set(prefs[i], isChecked(prefs[i])); } this._handleNoScript(false); if (Weave.Svc.Prefs.get("firstSync", "") == "notReady") Weave.Svc.Prefs.reset("firstSync"); Weave.Service.persistLogin(); Weave.Svc.Obs.notify("weave:service:setup-complete"); - if (this._settingUpNew) - gSyncUtils.openFirstClientFirstrun(); - else - gSyncUtils.openAddedClientFirstrun(); + + gSyncUtils.openFirstSyncProgressPage(); } Weave.Utils.nextTick(Weave.Service.sync, Weave.Service); + window.close(); }, onWizardCancel: function () { if (this._resettingSync) return; - if (this.wizard.pageIndex == SETUP_SUCCESS_PAGE) { - this.onWizardFinish(); - return; - } this.abortEasySetup(); this._handleNoScript(false); Weave.Service.startOver(); }, onSyncOptions: function () { this._beforeOptionsPage = this.wizard.pageIndex; this.wizard.pageIndex = OPTIONS_PAGE; @@ -709,17 +698,17 @@ var gSyncSetup = { onPairingStart: function onPairingStart() {}, onComplete: function onComplete(credentials) { Weave.Service.account = credentials.account; Weave.Service.password = credentials.password; Weave.Service.passphrase = credentials.synckey; Weave.Service.serverURL = credentials.serverURL; - self.wizard.pageIndex = SETUP_SUCCESS_PAGE; + gSyncSetup.wizardFinish(); }, onAbort: function onAbort(error) { delete self._jpakeclient; // Ignore if wizard is aborted. if (error == JPAKE_ERROR_USERABORT) return; @@ -901,35 +890,16 @@ var gSyncSetup = { if (valid) element.value = Weave.Service.serverURL; else Weave.Svc.Prefs.reset("serverURL"); return valid; }, - _handleSuccess: function() { - let self = this; - function fill(id, string) - document.getElementById(id).firstChild.nodeValue = - string ? self._stringBundle.GetStringFromName(string) : ""; - - fill("firstSyncAction", ""); - fill("firstSyncActionWarning", ""); - if (this._settingUpNew) { - fill("firstSyncAction", "newAccount.action.label"); - fill("firstSyncActionChange", "newAccount.change.label"); - return; - } - fill("firstSyncActionChange", "existingAccount.change.label"); - let action = document.getElementById("mergeChoiceRadio").selectedItem.id; - let id = action == "resetClient" ? "firstSyncAction" : "firstSyncActionWarning"; - fill(id, action + ".change.label"); - }, - _handleChoice: function () { let desc = document.getElementById("mergeChoiceRadio").selectedIndex; document.getElementById("chosenActionDeck").selectedIndex = desc; switch (desc) { case 1: if (this._case1Setup) break;
--- a/browser/base/content/syncSetup.xul +++ b/browser/base/content/syncSetup.xul @@ -20,16 +20,17 @@ # Portions created by the Initial Developer are Copyright (C) 2009 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Edward Lee <edilee@mozilla.com> # Mike Connor <mconnor@mozilla.com> # Paul O’Shannessy <paul@oshannessy.com> # Philipp von Weitershausen <philipp@weitershausen.de> +# Allison Naaktgeboren <ally@mozilla.com> # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your @@ -55,17 +56,16 @@ <wizard id="wizard" title="&accountSetupTitle.label;" windowtype="Weave:AccountSetup" persist="screenX screenY" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" onwizardnext="return gSyncSetup.onWizardAdvance()" onwizardback="return gSyncSetup.onWizardBack()" - onwizardfinish="gSyncSetup.onWizardFinish()" onwizardcancel="gSyncSetup.onWizardCancel()" onload="gSyncSetup.init()"> <script type="application/javascript" src="chrome://browser/content/syncSetup.js"/> <script type="application/javascript" src="chrome://browser/content/syncUtils.js"/> <script type="application/javascript" @@ -506,29 +506,16 @@ &confirm.server2.label; </description> <separator class="thin"/> <vbox id="clientList"> </vbox> </vbox> </deck> </wizardpage> - - <wizardpage label="&setup.successPage.title;" - id="successfulSetup" - onextra1="gSyncSetup.onSyncOptions()" - onpageshow="gSyncSetup.onPageShow()"> - <vbox align="center"> - <image id="successPageIcon"/> - </vbox> - <separator/> - <description class="normal"> - <html:span id="firstSyncAction">replace me</html:span> - <html:strong id="firstSyncActionWarning">replace me</html:strong> - <html:span id="firstSyncActionChange">replace me</html:span> - </description> - <description> - &continueUsing.label; - </description> - <separator flex="1"/> +# In terms of the wizard flow shown to the user, the 'syncOptionsConfirm' +# page above is not the last wizard page. To prevent the wizard binding from +# assuming that it is, we're inserting this dummy page here. This also means +# that the wizard needs to always be closed manually via wizardFinish(). + <wizardpage> </wizardpage> </wizard>
--- a/browser/base/content/syncUtils.js +++ b/browser/base/content/syncUtils.js @@ -104,27 +104,18 @@ let gSyncUtils = { openToS: function () { this._openLink(Weave.Svc.Prefs.get("termsURL")); }, openPrivacyPolicy: function () { this._openLink(Weave.Svc.Prefs.get("privacyURL")); }, - // xxxmpc - fix domain before 1.3 final (bug 583652) - _baseURL: "http://www.mozilla.com/firefox/sync/", - - openFirstClientFirstrun: function () { - let url = this._baseURL + "firstrun.html"; - this._openLink(url); - }, - - openAddedClientFirstrun: function () { - let url = this._baseURL + "secondrun.html"; - this._openLink(url); + openFirstSyncProgressPage: function () { + this._openLink("about:sync-progress"); }, /** * Prepare an invisible iframe with the passphrase backup document. * Used by both the print and saving methods. * * @param elid : ID of the form element containing the passphrase. * @param callback : Function called once the iframe has loaded.
--- a/browser/base/content/tabbrowser.css +++ b/browser/base/content/tabbrowser.css @@ -27,26 +27,16 @@ .tab-stack { vertical-align: top; /* for pinned tabs */ } tabpanels { background-color: transparent; } -.tab-drag-preview { - background: -moz-element(#content) left top; - background-clip: content-box; - background-size: cover; -} - -.tab-drag-panel[target] > .tab-drag-preview { - display: none; -} - .tab-drop-indicator { position: relative; z-index: 2; } .tab-throbber:not([busy]), .tab-throbber[busy] + .tab-icon-image { display: none;
--- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -1103,40 +1103,43 @@ <parameter name="aCharset"/> <parameter name="aPostData"/> <parameter name="aLoadInBackground"/> <parameter name="aAllowThirdPartyFixup"/> <body> <![CDATA[ var aFromExternal; var aRelatedToCurrent; + var aIsUTF8; if (arguments.length == 2 && typeof arguments[1] == "object" && !(arguments[1] instanceof Ci.nsIURI)) { let params = arguments[1]; aReferrerURI = params.referrerURI; aCharset = params.charset; aPostData = params.postData; aLoadInBackground = params.inBackground; aAllowThirdPartyFixup = params.allowThirdPartyFixup; aFromExternal = params.fromExternal; aRelatedToCurrent = params.relatedToCurrent; + aIsUTF8 = params.isUTF8; } var bgLoad = (aLoadInBackground != null) ? aLoadInBackground : Services.prefs.getBoolPref("browser.tabs.loadInBackground"); var owner = bgLoad ? null : this.selectedTab; var tab = this.addTab(aURI, { referrerURI: aReferrerURI, charset: aCharset, postData: aPostData, ownerTab: owner, allowThirdPartyFixup: aAllowThirdPartyFixup, fromExternal: aFromExternal, - relatedToCurrent: aRelatedToCurrent}); + relatedToCurrent: aRelatedToCurrent, + isUTF8: aIsUTF8}); if (!bgLoad) this.selectedTab = tab; return tab; ]]> </body> </method> @@ -1199,28 +1202,30 @@ <parameter name="aPostData"/> <parameter name="aOwner"/> <parameter name="aAllowThirdPartyFixup"/> <body> <![CDATA[ var aFromExternal; var aRelatedToCurrent; var aSkipAnimation; + var aIsUTF8; if (arguments.length == 2 && typeof arguments[1] == "object" && !(arguments[1] instanceof Ci.nsIURI)) { let params = arguments[1]; aReferrerURI = params.referrerURI; aCharset = params.charset; aPostData = params.postData; aOwner = params.ownerTab; aAllowThirdPartyFixup = params.allowThirdPartyFixup; aFromExternal = params.fromExternal; aRelatedToCurrent = params.relatedToCurrent; aSkipAnimation = params.skipAnimation; + aIsUTF8 = params.isUTF8; } this._browsers = null; // invalidate cache // if we're adding tabs, we're past interrupt mode, ditch the owner if (this.mCurrentTab.owner) this.mCurrentTab.owner = null; @@ -1358,16 +1363,18 @@ // the document successfully loads b.userTypedValue = aURI; let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; if (aAllowThirdPartyFixup) flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; if (aFromExternal) flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL; + if (aIsUTF8) + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8; try { b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData); } catch (ex) { Cu.reportError(ex); } } // We start our browsers out as inactive, and then maintain @@ -1495,19 +1502,16 @@ return; } var isLastTab = (this.tabs.length - this._removingTabs.length == 1); if (!this._beginRemoveTab(aTab, false, null, true)) return; - if (this.tabContainer.draggedTab == aTab) - this.tabContainer._endTabDrag(); - if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse) this.tabContainer._lockTabSizing(aTab); else this.tabContainer._unlockTabSizing(); if (!animate /* the caller didn't opt in */ || isLastTab || aTab.pinned || @@ -1699,18 +1703,16 @@ this.tabContainer._positionPinnedTabs(); // update tab close buttons state this.tabContainer.adjustTabstrip(); setTimeout(function(tabs) { tabs._lastTabClosedByMouse = false; }, 0, this.tabContainer); - - this.tabContainer._handleTabDrag(); // Update drag feedback. } // update first-tab/last-tab/beforeselected/afterselected attributes this.selectedTab._selected = true; // Removing the panel requires fixing up selectedPanel immediately // (see below), which would be hindered by the potentially expensive // browser removal. So we remove the browser and the panel in two @@ -2432,17 +2434,17 @@ offset *= -1; this.tabContainer.advanceSelectedTab(offset, true); aEvent.stopPropagation(); aEvent.preventDefault(); } #else if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey && aEvent.keyCode == KeyEvent.DOM_VK_F4 && - this.mTabBox.handleCtrlPageUpDown) { + !this.mCurrentTab.pinned) { this.removeCurrentTab({animate: true}); aEvent.stopPropagation(); aEvent.preventDefault(); } #endif ]]></body> </method> @@ -2454,17 +2456,17 @@ onget="return this.mCurrentBrowser.userTypedValue;" onset="return this.mCurrentBrowser.userTypedValue = val;"/> <method name="createTooltip"> <parameter name="event"/> <body><![CDATA[ event.stopPropagation(); var tab = document.tooltipNode; - if (tab.localName != "tab" || this.tabContainer.draggedTab) { + if (tab.localName != "tab") { event.preventDefault(); return; } event.target.setAttribute("label", tab.mOverCloseButton ? tab.getAttribute("closetabtext") : tab.getAttribute("label")); ]]></body> </method> @@ -2679,57 +2681,49 @@ <body><![CDATA[ return !tab.pinned && !tab.hidden; ]]></body> </method> </implementation> <handlers> <handler event="underflow" phase="capturing"><![CDATA[ - if (event.originalTarget != this._scrollbox || event.detail == 0) + if (event.detail == 0) return; // Ignore vertical events var tabs = document.getBindingParent(this); tabs.removeAttribute("overflow"); if (tabs._lastTabClosedByMouse) tabs._expandSpacerBy(this._scrollButtonDown.clientWidth); tabs.tabbrowser._removingTabs.forEach(tabs.tabbrowser.removeTab, tabs.tabbrowser); tabs._positionPinnedTabs(); ]]></handler> <handler event="overflow"><![CDATA[ - if (event.originalTarget != this._scrollbox || event.detail == 0) + if (event.detail == 0) return; // Ignore vertical events var tabs = document.getBindingParent(this); tabs.setAttribute("overflow", "true"); tabs._positionPinnedTabs(); ]]></handler> </handlers> </binding> <binding id="tabbrowser-tabs" extends="chrome://global/content/bindings/tabbox.xml#tabs"> <resources> <stylesheet src="chrome://browser/content/tabbrowser.css"/> </resources> - <!-- The onpopupshowing/hiding handlers on the panel are to circumvent - noautohide=true disabling level=top on Linux. See bug 448929. --> <content> <xul:hbox align="end"> - <xul:panel class="tab-drag-panel" anonid="tab-drag-panel" hidden="true" level="top" - onpopupshowing="this.setAttribute('noautohide', true);" - onpopuphiding="this.removeAttribute('noautohide');"> - <xul:label class="tab-drag-label" crop="end"/> - <xul:box class="tab-drag-preview"/> - </xul:panel> <xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/> </xul:hbox> <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1" style="min-width: 1px;" #ifndef XP_MACOSX clicktoscroll="true" #endif class="tabbrowser-arrowscrollbox"> @@ -2815,476 +2809,16 @@ } });]]></field> <field name="_blockDblClick">false</field> <field name="_tabDropIndicator"> document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator"); </field> - <method name="_positionDropIndicator"> - <parameter name="event"/> - <parameter name="scrollOnly"/> - <body><![CDATA[ - var effects = event.dataTransfer ? this._setEffectAllowedForDataTransfer(event) : ""; - - var ind = this._tabDropIndicator; - if (effects == "none") { - ind.collapsed = true; - return; - } - event.preventDefault(); - event.stopPropagation(); - - var tabStrip = this.mTabstrip; - var ltr = (window.getComputedStyle(this).direction == "ltr"); - - // Autoscroll the tab strip if we drag over the scroll - // buttons, even if we aren't dragging a tab, but then - // return to avoid drawing the drop indicator. - var pixelsToScroll = 0; - var target = event.originalTarget; - if (target.ownerDocument == document && - this.getAttribute("overflow") == "true") { - let targetAnonid = target.getAttribute("anonid"); - switch (targetAnonid) { - case "scrollbutton-up": - pixelsToScroll = tabStrip.scrollIncrement * -1; - break; - case "scrollbutton-down": - pixelsToScroll = tabStrip.scrollIncrement; - break; - } - if (pixelsToScroll) { - if (effects) - tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll); - else - tabStrip._startScroll(pixelsToScroll < 0 ? -1 : 1); - } - } - - if (scrollOnly) { - ind.collapsed = true; - return; - } - - if (effects == "link") { - let tab = this._getDragTargetTab(event); - if (tab) { - if (!this._dragTime) - this._dragTime = Date.now(); - if (Date.now() >= this._dragTime + this._dragOverDelay) - this.selectedItem = tab; - ind.collapsed = true; - return; - } - } - - var newIndex = this._getDropIndex(event); - var scrollRect = tabStrip.scrollClientRect; - var rect = this.getBoundingClientRect(); - var minMargin = scrollRect.left - rect.left; - var maxMargin = Math.min(minMargin + scrollRect.width, - scrollRect.right); - if (!ltr) - [minMargin, maxMargin] = [this.clientWidth - maxMargin, - this.clientWidth - minMargin]; - var newMargin; - if (pixelsToScroll) { - // If we are scrolling, put the drop indicator at the edge, - // so that it doesn't jump while scrolling. - newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin; - } - else { - if (newIndex == this.childNodes.length) { - let tabRect = this.childNodes[newIndex-1].getBoundingClientRect(); - if (ltr) - newMargin = tabRect.right - rect.left; - else - newMargin = rect.right - tabRect.left; - } - else { - let tabRect = this.childNodes[newIndex].getBoundingClientRect(); - if (ltr) - newMargin = tabRect.left - rect.left; - else - newMargin = rect.right - tabRect.right; - } - } - - ind.collapsed = false; - - newMargin += ind.clientWidth / 2; - if (!ltr) - newMargin *= -1; - - ind.style.MozTransform = "translate(" + Math.round(newMargin) + "px)"; - ind.style.MozMarginStart = (-ind.clientWidth) + "px"; - ]]></body> - </method> - - <field name="_tabDragPanel"> - document.getAnonymousElementByAttribute(this, "anonid", "tab-drag-panel"); - </field> - - <field name="draggedTab">null</field> - - <method name="_handleTabDrag"> - <parameter name="event"/> - <body><![CDATA[ - let draggedTab = this.draggedTab; - if (!draggedTab) - return; - - if (event) - draggedTab._dragData._savedEvent = event; - else - event = draggedTab._dragData._savedEvent; - - if (this._updateTabDetachState(event, draggedTab)) - return; - - // Keep the dragged tab visually within the region of like tabs. - let tabs = this.tabbrowser.visibleTabs; - let numPinned = this.tabbrowser._numPinnedTabs; - let leftmostTab = draggedTab.pinned ? tabs[0] : tabs[numPinned]; - let rightmostTab = draggedTab.pinned ? tabs[numPinned-1] : tabs[tabs.length-1]; - let tabWidth = draggedTab.getBoundingClientRect().width; - let ltr = (window.getComputedStyle(this).direction == "ltr"); - if (!ltr) - [leftmostTab, rightmostTab] = [rightmostTab, leftmostTab]; - let left = leftmostTab.boxObject.screenX; - let right = rightmostTab.boxObject.screenX + tabWidth; - let transformX = event.screenX - draggedTab._dragData._dragStartX; - if (!draggedTab.pinned) - transformX += this.mTabstrip.scrollPosition; - let tabX = draggedTab.boxObject.screenX + transformX; - draggedTab._dragData._dragDistX = transformX; - if (tabX < left) - transformX += left - tabX; - // Prevent unintended overflow, especially in RTL mode. - else if (tabX + tabWidth > right) - transformX += right - tabX - tabWidth - (ltr ? 0 : 1); - draggedTab.style.MozTransform = "translate(" + transformX + "px)"; - - let newIndex = this._getDropIndex(event, draggedTab); - let tabAtNewIndex = this.childNodes[newIndex > draggedTab._tPos ? - newIndex-1 : newIndex]; - this._positionDropIndicator(event, tabAtNewIndex.pinned == draggedTab.pinned); - - if (newIndex == draggedTab._dragData._dropIndex) - return; - draggedTab._dragData._dropIndex = newIndex; - - if (!ltr) - tabWidth *= -1; - tabs.forEach(function(tab) { - if (tab == draggedTab || tab.pinned != draggedTab.pinned) - return; - else if (tab._tPos < draggedTab._tPos && tab._tPos >= newIndex) - tab.style.MozTransform = "translate(" + tabWidth + "px)"; - else if (tab._tPos > draggedTab._tPos && tab._tPos < newIndex) - tab.style.MozTransform = "translate(" + -tabWidth + "px)"; - else - tab.style.MozTransform = ""; - }); - ]]></body> - </method> - - <method name="_updateTabDetachState"> - <parameter name="event"/> - <parameter name="draggedTab"/> - <body><![CDATA[ - let data = draggedTab._dragData; - if (data._targetWindow.closed) { - data._targetWindow = window; - window.focus(); - } - else if (data._dropTarget) { - data._dropTarget._tabDropIndicator.collapsed = true; - } - delete data._dropTarget; - - function isEventOutsideWindow(event, win) { - return (event.screenX < win.screenX || event.screenX >= win.screenX + win.outerWidth || - event.screenY < win.screenY || event.screenY >= win.screenY + win.outerHeight); - } - if (isEventOutsideWindow(event, data._targetWindow) || - Services.ww.activeWindow != data._targetWindow) { - // Iterate through browser windows in hopefully front-to-back order. - let winEnum = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true); - let wins = []; - while (winEnum.hasMoreElements()) - wins.push(winEnum.getNext()); - // Work around broken z-order enumerator on Linux. See bug 156333. - if (!wins.length) { - winEnum = Services.wm.getEnumerator("navigator:browser"); - while (winEnum.hasMoreElements()) - wins.unshift(winEnum.getNext()); - } - wins.every(function(win) { - if (win.closed || win.windowState == STATE_MINIMIZED || - isEventOutsideWindow(event, win)) - return true; - data._targetWindow = win; - win.focus(); // Raise window when cursor moves over it. - }); - } - - let detached = (this.getAttribute("drag") == "detach"); - let loneTab = (this.childElementCount == 1); - let bo = loneTab ? draggedTab.boxObject : this.parentNode.boxObject; - // Detach tab if outside window, to the left or right of tab strip, or - // at least one tab height above or below it. - if (data._targetWindow != window || - event.screenX < bo.screenX || event.screenX >= bo.screenX + bo.width || - event.screenY < bo.screenY - (detached || loneTab ? 0 : 1) * bo.height || - event.screenY >= bo.screenY + (detached || loneTab ? 1 : 2) * bo.height) { - if (data._targetWindow != window && - data._targetWindow.windowState != STATE_MINIMIZED) { - let that = data._targetWindow.gBrowser.tabContainer; - let bo = that.parentNode.boxObject; - if (event.screenX >= bo.screenX && event.screenX < bo.screenX + bo.width && - event.screenY >= bo.screenY && event.screenY < bo.screenY + bo.height) { - that._positionDropIndicator(event); - data._dropTarget = that; - } - } - - let dragPanel = this._tabDragPanel; - if (data._dropTarget) - dragPanel.setAttribute("target", "true"); - else - dragPanel.removeAttribute("target"); - - if (!detached) { - this.setAttribute("drag", "detach"); - this._clearDragTransforms(); - this._tabDropIndicator.collapsed = true; - if (draggedTab.style.maxWidth) { - data._maxWidth = draggedTab.style.maxWidth; - draggedTab.style.maxWidth = ""; - } - delete data._dropIndex; - let label = dragPanel.firstChild; - let preview = dragPanel.lastChild; - label.value = draggedTab.label; - label.width = preview.width = Math.min(outerWidth / 2, screen.availWidth / 5); - let aspectRatio = this.tabbrowser.clientWidth / this.tabbrowser.clientHeight; - preview.height = Math.min(preview.width / aspectRatio, screen.availHeight / 5); - dragPanel.hidden = false; - dragPanel.openPopupAtScreen(event.screenX, event.screenY, false); - } - let width = dragPanel.clientWidth; - let [left, top] = this._getAdjustedCoords(event.screenX, event.screenY, width, - dragPanel.clientHeight, width / 2, 12, true); - dragPanel.moveTo(left, top); - return true; - } - if (detached) { // Otherwise, put tab back in the tab strip. - this.setAttribute("drag", "move"); - if (data._maxWidth) { - draggedTab.style.setProperty("max-width", data._maxWidth, "important"); - delete draggedTab._maxWidth; - } - this.mTabstrip._updateScrollButtonsDisabledState(); - this._tabDragPanel.hidePopup(); - } - ]]></body> - </method> - - <method name="_getAdjustedCoords"> - <parameter name="aLeft"/> - <parameter name="aTop"/> - <parameter name="aWidth"/> - <parameter name="aHeight"/> - <parameter name="aOffsetX"/> - <parameter name="aOffsetY"/> - <parameter name="isPanel"/> - <body><![CDATA[ - // screen.availTop et al. only check the source window's screen, but - // we want to look at the target window's screen. - let sX = {}, sY = {}, sWidth = {}, sHeight = {}; - Cc["@mozilla.org/gfx/screenmanager;1"] - .getService(Ci.nsIScreenManager) - .screenForRect(aLeft, aTop, 1, 1) - .GetAvailRect(sX, sY, sWidth, sHeight); - // Window manager repositions panels that are too close to the right - // or bottom of a screen, so leave a gutter to avoid that. - if (isPanel) { - sWidth.value -= 3; - sHeight.value -= 3; - } - // Ensure rect will be entirely onscreen. - let width = Math.min(aWidth, sWidth.value); - let height = Math.min(aHeight, sHeight.value); - let left = Math.min(Math.max(aLeft - aOffsetX, sX.value), - sX.value + sWidth.value - width); - let top = Math.min(Math.max(aTop - aOffsetY, sY.value), - sY.value + sHeight.value - height); - return [left, top, width, height]; - ]]></body> - </method> - - <method name="_handleTabDrop"> - <parameter name="event"/> - <body><![CDATA[ - let draggedTab = this.draggedTab; - if (this.getAttribute("drag") == "move") { - this._slideTab(event, draggedTab); - return; - } - - do { - let that = draggedTab._dragData._dropTarget; - let win = draggedTab._dragData._targetWindow; - if (!that || win.closed) - break; - that._tabDropIndicator.collapsed = true; - if (win.windowState == STATE_MINIMIZED) - break; - this._endTabDrag(); - - // User dropped the tab onto another window's tab strip, so swap the - // tab with a new one we create in that window, and then close it in - // this window (making it seem to have moved between windows). - let newIndex = that._getDropIndex(event); - let newTab = that.tabbrowser.addTab("about:blank"); - let newBrowser = that.tabbrowser.getBrowserForTab(newTab); - newBrowser.stop(); // Stop the about:blank load. - newBrowser.docShell; // Make sure it has a docshell. - let numPinned = that.tabbrowser._numPinnedTabs; - if (newIndex < numPinned || draggedTab.pinned && newIndex == numPinned) - that.tabbrowser.pinTab(newTab); - that.tabbrowser.moveTabTo(newTab, newIndex); - that.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab); - - // We need to select the tab after we've done - // swapBrowsersAndCloseOther, so that the updateCurrentBrowser - // it triggers will correctly update our URL bar. - that.selectedItem = newTab; - return; - } while (false); - - let [left, top, width, height] = this._getAdjustedCoords( - event.screenX, event.screenY, outerWidth, outerHeight, - this._tabDragPanel.clientWidth / 2, draggedTab._dragData._dragOffsetY); - this._endTabDrag(); - - if (this.childElementCount == 1) { - // Resize _before_ move to ensure the window fits the new screen. If - // the window is too large for its screen, the window manager may do - // automatic repositioning. - window.resizeTo(width, height); - window.moveTo(left, top); - window.focus(); - return; - } - - draggedTab.collapsed = true; - this.tabbrowser.replaceTabWithWindow(draggedTab, { screenX: left, - screenY: top, -#ifndef XP_WIN - outerWidth: width, - outerHeight: height -#endif - }); - ]]></body> - </method> - - <method name="_slideTab"> - <parameter name="event"/> - <parameter name="draggedTab"/> - <body><![CDATA[ - let oldIndex = draggedTab._tPos; - let newIndex = draggedTab._dragData._dropIndex; - if (newIndex > oldIndex) - newIndex--; - this.removeAttribute("drag"); - this._endTabDrag(); - - if (!draggedTab.pinned && newIndex < this.tabbrowser._numPinnedTabs) - this.tabbrowser.pinTab(draggedTab); - else if (draggedTab.pinned && newIndex >= this.tabbrowser._numPinnedTabs) - this.tabbrowser.unpinTab(draggedTab); - else if (Services.prefs.getBoolPref("browser.tabs.animate")) { - let difference = 0; - // Calculate number of visible tabs between start and destination. - if (newIndex != oldIndex) { - let tabs = this.tabbrowser.visibleTabs; - for (let i = 0; i < tabs.length; i++) { - let position = tabs[i]._tPos; - if (position <= newIndex && position > oldIndex) - difference++; - else if (position >= newIndex && position < oldIndex) - difference--; - } - } - let displacement = difference * draggedTab.getBoundingClientRect().width; - if (window.getComputedStyle(this).direction == "rtl") - displacement *= -1; - let destination = "translate(" + displacement + "px)"; - if (draggedTab.style.MozTransform != destination) { - this.setAttribute("drag", "finish"); - draggedTab.style.MozTransform = destination; - draggedTab.addEventListener("transitionend", function finish(event) { - if (event.eventPhase != Event.AT_TARGET || - event.propertyName != "-moz-transform") - return; - draggedTab.removeEventListener("transitionend", finish); - draggedTab.removeAttribute("dragged"); - let that = draggedTab.parentNode; - that.removeAttribute("drag"); - that._clearDragTransforms(); - that.tabbrowser.moveTabTo(draggedTab, newIndex); - }); - return; - } - } - draggedTab.removeAttribute("dragged"); - this._clearDragTransforms(); - this.tabbrowser.moveTabTo(draggedTab, newIndex); - ]]></body> - </method> - - <method name="_endTabDrag"> - <body><![CDATA[ - let tab = this.draggedTab; - if (!tab) - return; - this.draggedTab = null; - delete tab._dragData; - - document.removeEventListener("mousemove", this); - document.removeEventListener("mouseup", this); - this.removeEventListener("TabSelect", this); - this.mTabstrip.removeEventListener("scroll", this); - - this._tabDragPanel.hidePopup(); - this._tabDragPanel.hidden = true; - this._tabDropIndicator.collapsed = true; - - if (this.hasAttribute("drag")) { - if (this.getAttribute("drag") == "detach") - this._unlockTabSizing(); - tab.removeAttribute("dragged"); - this.removeAttribute("drag"); - this._clearDragTransforms(); - } - ]]></body> - </method> - - <method name="_clearDragTransforms"> - <body> - this.tabbrowser.visibleTabs.forEach(function(visibleTab) { - visibleTab.style.MozTransform = ""; - }); - </body> - </method> - <field name="_dragOverDelay">350</field> <field name="_dragTime">0</field> <field name="_container" readonly="true"><![CDATA[ this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this; ]]></field> <property name="visible" @@ -3435,34 +2969,37 @@ tab.style.setProperty("max-width", tabWidth, "important"); if (!isEndTab) { // keep tabs the same width tab.style.MozTransition = "none"; tab.clientTop; // flush styles to skip animation; see bug 649247 tab.style.MozTransition = ""; } } this._hasTabTempMaxWidth = true; - window.addEventListener("mouseout", this); + this.tabbrowser.addEventListener("mousemove", this, false); + window.addEventListener("mouseout", this, false); } ]]></body> </method> <method name="_expandSpacerBy"> <parameter name="pixels"/> <body><![CDATA[ let spacer = this._closingTabsSpacer; spacer.style.width = parseFloat(spacer.style.width) + pixels + "px"; this._usingClosingTabsSpacer = true; - window.addEventListener("mouseout", this); + this.tabbrowser.addEventListener("mousemove", this, false); + window.addEventListener("mouseout", this, false); ]]></body> </method> <method name="_unlockTabSizing"> <body><![CDATA[ - window.removeEventListener("mouseout", this); + this.tabbrowser.removeEventListener("mousemove", this, false); + window.removeEventListener("mouseout", this, false); if (this._hasTabTempMaxWidth) { this._hasTabTempMaxWidth = false; let tabs = this.tabbrowser.visibleTabs; for (let i = 0; i < tabs.length; i++) tabs[i].style.maxWidth = ""; } if (this._usingClosingTabsSpacer) { this._usingClosingTabsSpacer = false; @@ -3519,46 +3056,24 @@ this.adjustTabstrip(); this._fillTrailingGap(); this._handleTabSelect(); this.mTabstripWidth = width; } this.tabbrowser.updateWindowResizers(); break; case "mouseout": - if (this.draggedTab) - break; - let bo = this.mTabstrip.boxObject; - if (aEvent.screenX >= bo.screenX && aEvent.screenX < bo.screenX + bo.width && - aEvent.screenY >= bo.screenY && aEvent.screenY < bo.screenY + bo.height) - break; - let tabContextMenu = document.getElementById("tabContextMenu"); - if (tabContextMenu.state == "open") - tabContextMenu.addEventListener("popuphidden", this); - else - this._unlockTabSizing(); - break; - case "popuphidden": // Tab context menu was closed. - if (aEvent.eventPhase != Event.AT_TARGET) + // If the "related target" (the node to which the pointer went) is not + // a child of the current document, the mouse just left the window. + let relatedTarget = aEvent.relatedTarget; + if (relatedTarget && relatedTarget.ownerDocument == document) break; - aEvent.target.removeEventListener("popuphidden", this); - this._unlockTabSizing(); - break; case "mousemove": - this._handleTabDrag(aEvent); - break; - case "mouseup": - this._handleTabDrop(aEvent); - break; - case "TabSelect": // Focus was stolen from dragged tab! - this._endTabDrag(aEvent); - window.focus(); - break; - case "scroll": // Tab strip was scrolled. - this._handleTabDrag(); + if (document.getElementById("tabContextMenu").state != "open") + this._unlockTabSizing(); break; } ]]></body> </method> <field name="_animateElement"> this.mTabstrip._scrollButtonDown; </field> @@ -3615,74 +3130,83 @@ return null; } return tab; ]]></body> </method> <method name="_getDropIndex"> <parameter name="event"/> - <parameter name="draggedTab"/> <body><![CDATA[ - function compare(a, b, lessThan) lessThan ? a < b : a > b; - let ltr = (window.getComputedStyle(this).direction == "ltr"); - let eX = event.screenX; - let tabs = this.tabbrowser.visibleTabs; - - if (draggedTab) { - let dist = draggedTab._dragData._dragDistX; - let tabX = draggedTab.boxObject.screenX + dist; - let draggingRight = dist > 0; - if (draggingRight) - tabX += draggedTab.boxObject.width; - // iterate through app tabs first, since their z-index is higher - else if (!draggedTab.pinned) - for (let i = 0, numPinned = this.tabbrowser._numPinnedTabs; i < numPinned; i++) - if (compare(eX, tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2, ltr)) - return i; - - let i = tabs.indexOf(draggedTab), tab = draggedTab, next; - while (next = ltr ^ draggingRight ? tabs[--i] : tabs[++i]) { - let x = next.pinned == draggedTab.pinned ? tabX : eX; - let middleOfNextTab = next.boxObject.screenX + next.boxObject.width / 2; - if (!compare(x, middleOfNextTab, !draggingRight)) - break; - // ensure an app tab is actually inside the normal tab region - if (draggedTab.pinned && !next.pinned && - x < this.mTabstrip._scrollButtonUp.boxObject.screenX) - break; - tab = next; - } - return tab._tPos + (ltr ^ draggingRight ? 0 : 1); + var tabs = this.childNodes; + var tab = this._getDragTargetTab(event); + if (window.getComputedStyle(this, null).direction == "ltr") { + for (let i = tab ? tab._tPos : 0; i < tabs.length; i++) + if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2) + return i; + } else { + for (let i = tab ? tab._tPos : 0; i < tabs.length; i++) + if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2) + return i; } - - let tab = this._getDragTargetTab(event); - for (let i = tab ? tab._tPos : 0; i < tabs.length; i++) - if (compare(eX, tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2, ltr)) - return tabs[i]._tPos; - return this.childElementCount; + return tabs.length; ]]></body> </method> <method name="_setEffectAllowedForDataTransfer"> <parameter name="event"/> <body><![CDATA[ var dt = event.dataTransfer; // Disallow dropping multiple items if (dt.mozItemCount > 1) return dt.effectAllowed = "none"; + var types = dt.mozTypesAt(0); + var sourceNode = null; + // tabs are always added as the first type + if (types[0] == TAB_DROP_TYPE) { + var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0); + if (sourceNode instanceof XULElement && + sourceNode.localName == "tab" && + (sourceNode.parentNode == this || + (sourceNode.ownerDocument.defaultView instanceof ChromeWindow && + sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser"))) { + if (sourceNode.parentNode == this && + (event.screenX >= sourceNode.boxObject.screenX && + event.screenX <= (sourceNode.boxObject.screenX + + sourceNode.boxObject.width))) { + return dt.effectAllowed = "none"; + } + + return dt.effectAllowed = "copyMove"; + } + } + if (browserDragAndDrop.canDropLink(event)) { // Here we need to do this manually return dt.effectAllowed = dt.dropEffect = "link"; } return dt.effectAllowed = "none"; ]]></body> </method> + <method name="_continueScroll"> + <parameter name="event"/> + <body><![CDATA[ + // Workaround for bug 481904: Dragging a tab stops scrolling at + // the tab's position when dragging to the first/last tab and back. + var t = this.selectedItem; + if (event.screenX >= t.boxObject.screenX && + event.screenX <= t.boxObject.screenX + t.boxObject.width && + event.screenY >= t.boxObject.screenY && + event.screenY <= t.boxObject.screenY + t.boxObject.height) + this.mTabstrip.ensureElementIsVisible(t); + ]]></body> + </method> + <method name="_handleNewTab"> <parameter name="tab"/> <body><![CDATA[ if (tab.parentNode != this) return; tab._fullyOpen = true; this.adjustTabstrip(); @@ -3806,109 +3330,306 @@ // shortcuts only. return; } event.stopPropagation(); event.preventDefault(); ]]></handler> <handler event="dragstart"><![CDATA[ - if (this.draggedTab) - return; var tab = this._getDragTargetTab(event); - if (!tab || !tab._fullyOpen || tab.closing) - return; - -#ifdef XP_MACOSX - if (event.altKey) { -#else - if (event.ctrlKey) { -#endif - let dt = event.dataTransfer; - let browser = tab.linkedBrowser; - dt.setData("text/x-moz-url", browser.currentURI.spec + "\n" + browser.contentTitle); - let favicon = document.getAnonymousElementByAttribute(tab, "class", "tab-icon-image"); - dt.setDragImage(favicon, 16, 16); + if (!tab) return; - } - - this.setAttribute("drag", "move"); - this.draggedTab = tab; - tab.setAttribute("dragged", "true"); - let data = tab._dragData = {}; - data._dragStartX = event.screenX; - if (!tab.pinned) - data._dragStartX += this.mTabstrip.scrollPosition; - data._dragDistX = 0; - data._dragOffsetY = event.screenY - window.screenY; - data._dropIndex = tab._tPos; - data._savedEvent = event; - data._targetWindow = window; - - document.addEventListener("mousemove", this); - document.addEventListener("mouseup", this); - this.addEventListener("TabSelect", this); - this.mTabstrip.addEventListener("scroll", this); + + let dt = event.dataTransfer; + dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0); + let uri = this.tabbrowser.getBrowserForTab(tab).currentURI; + let spec = uri ? uri.spec : "about:blank"; + + // We must not set text/x-moz-url or text/plain data here, + // otherwise trying to deatch the tab by dropping it on the desktop + // may result in an "internet shortcut" + dt.mozSetDataAt("text/x-moz-text-internal", spec, 0); + + // Set the cursor to an arrow during tab drags. + dt.mozCursor = "default"; + + let canvas = tabPreviews.capture(tab, false); + dt.setDragImage(canvas, 0, 0); + + // _dragOffsetX/Y give the coordinates that the mouse should be + // positioned relative to the corner of the new window created upon + // dragend such that the mouse appears to have the same position + // relative to the corner of the dragged tab. + function clientX(ele) ele.getBoundingClientRect().left; + let tabOffsetX = clientX(tab) - + clientX(this.children[0].pinned ? this.children[0] : this); + tab._dragOffsetX = event.screenX - window.screenX - tabOffsetX; + tab._dragOffsetY = event.screenY - window.screenY; event.stopPropagation(); ]]></handler> - <handler event="dragover" action="this._positionDropIndicator(event);"/> - - <handler event="drop"><![CDATA[ - this._tabDropIndicator.collapsed = true; - - let dt = event.dataTransfer; - if (dt.dropEffect != "link") - return; - - let url = browserDragAndDrop.drop(event, { }); - - // Disallow dropping strings that contain spaces (not a valid url - // character). Also disallow dropping javascript: or data: urls. - if (!url || !url.length || url.indexOf(" ") != -1 || - /^\s*(javascript|data):/.test(url)) + <handler event="dragover"><![CDATA[ + var effects = this._setEffectAllowedForDataTransfer(event); + + var ind = this._tabDropIndicator; + if (effects == "" || effects == "none") { + ind.collapsed = true; + this._continueScroll(event); return; - - let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); - - if (event.shiftKey) - bgLoad = !bgLoad; - - let tab = this._getDragTargetTab(event); - if (!tab) { - // We're adding a new tab. - let newIndex = this._getDropIndex(event); - let newTab = this.tabbrowser.loadOneTab(getShortcutOrURI(url), {inBackground: bgLoad}); - this.tabbrowser.moveTabTo(newTab, newIndex); - } else { - // Load in an existing tab. - try { - this.tabbrowser.getBrowserForTab(tab).loadURI(getShortcutOrURI(url)); - if (!bgLoad) + } + event.preventDefault(); + event.stopPropagation(); + + var tabStrip = this.mTabstrip; + var ltr = (window.getComputedStyle(this, null).direction == "ltr"); + + // autoscroll the tab strip if we drag over the scroll + // buttons, even if we aren't dragging a tab, but then + // return to avoid drawing the drop indicator + var pixelsToScroll = 0; + if (this.getAttribute("overflow") == "true") { + var targetAnonid = event.originalTarget.getAttribute("anonid"); + switch (targetAnonid) { + case "scrollbutton-up": + pixelsToScroll = tabStrip.scrollIncrement * -1; + break; + case "scrollbutton-down": + pixelsToScroll = tabStrip.scrollIncrement; + break; + } + if (pixelsToScroll) + tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll); + } + + if (effects == "link") { + let tab = this._getDragTargetTab(event); + if (tab) { + if (!this._dragTime) + this._dragTime = Date.now(); + if (Date.now() >= this._dragTime + this._dragOverDelay) this.selectedItem = tab; ind.collapsed = true; return; - } catch(ex) { - // Just ignore invalid urls. + } + } + + var newIndex = this._getDropIndex(event); + var scrollRect = tabStrip.scrollClientRect; + var rect = this.getBoundingClientRect(); + var minMargin = scrollRect.left - rect.left; + var maxMargin = Math.min(minMargin + scrollRect.width, + scrollRect.right); + if (!ltr) + [minMargin, maxMargin] = [this.clientWidth - maxMargin, + this.clientWidth - minMargin]; + var newMargin; + if (pixelsToScroll) { + // if we are scrolling, put the drop indicator at the edge + // so that it doesn't jump while scrolling + newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin; + } + else { + if (newIndex == this.childNodes.length) { + let tabRect = this.childNodes[newIndex-1].getBoundingClientRect(); + if (ltr) + newMargin = tabRect.right - rect.left; + else + newMargin = rect.right - tabRect.left; + } + else { + let tabRect = this.childNodes[newIndex].getBoundingClientRect(); + if (ltr) + newMargin = tabRect.left - rect.left; + else + newMargin = rect.right - tabRect.right; } } + + ind.collapsed = false; + + newMargin += ind.clientWidth / 2; + if (!ltr) + newMargin *= -1; + + ind.style.MozTransform = "translate(" + Math.round(newMargin) + "px)"; + ind.style.MozMarginStart = (-ind.clientWidth) + "px"; + ]]></handler> + + <handler event="drop"><![CDATA[ + var dt = event.dataTransfer; + var dropEffect = dt.dropEffect; + var draggedTab; + if (dropEffect != "link") { // copy or move + draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0); + // not our drop then + if (!draggedTab) + return; + } + + this._tabDropIndicator.collapsed = true; + event.stopPropagation(); + + if (draggedTab && (dropEffect == "copy" || + draggedTab.parentNode == this)) { + let newIndex = this._getDropIndex(event); + if (dropEffect == "copy") { + // copy the dropped tab (wherever it's from) + let newTab = this.tabbrowser.duplicateTab(draggedTab); + this.tabbrowser.moveTabTo(newTab, newIndex); + if (draggedTab.parentNode != this || event.shiftKey) + this.selectedItem = newTab; + } else { + // move the dropped tab + if (newIndex > draggedTab._tPos) + newIndex--; + + if (draggedTab.pinned) { + if (newIndex >= this.tabbrowser._numPinnedTabs) + this.tabbrowser.unpinTab(draggedTab); + } else { + if (newIndex <= this.tabbrowser._numPinnedTabs - 1) + this.tabbrowser.pinTab(draggedTab); + } + + this.tabbrowser.moveTabTo(draggedTab, newIndex); + } + } else if (draggedTab) { + // swap the dropped tab with a new one we create and then close + // it in the other window (making it seem to have moved between + // windows) + let newIndex = this._getDropIndex(event); + let newTab = this.tabbrowser.addTab("about:blank"); + let newBrowser = this.tabbrowser.getBrowserForTab(newTab); + // Stop the about:blank load + newBrowser.stop(); + // make sure it has a docshell + newBrowser.docShell; + + this.tabbrowser.moveTabTo(newTab, newIndex); + + this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab); + + // We need to select the tab after we've done + // swapBrowsersAndCloseOther, so that the updateCurrentBrowser + // it triggers will correctly update our URL bar. + this.tabbrowser.selectedTab = newTab; + } else { + let url = browserDragAndDrop.drop(event, { }); + + // valid urls don't contain spaces ' '; if we have a space it isn't a valid url. + // Also disallow dropping javascript: or data: urls--bail out + if (!url || !url.length || url.indexOf(" ", 0) != -1 || + /^\s*(javascript|data):/.test(url)) + return; + + let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); + + if (event.shiftKey) + bgLoad = !bgLoad; + + let tab = this._getDragTargetTab(event); + if (!tab || dropEffect == "copy") { + // We're adding a new tab. + let newIndex = this._getDropIndex(event); + let newTab = this.tabbrowser.loadOneTab(getShortcutOrURI(url), {inBackground: bgLoad}); + this.tabbrowser.moveTabTo(newTab, newIndex); + } else { + // Load in an existing tab. + try { + this.tabbrowser.getBrowserForTab(tab).loadURI(getShortcutOrURI(url)); + if (!bgLoad) + this.selectedItem = tab; + } catch(ex) { + // Just ignore invalid urls + } + } + } + + // these offsets are only used in dragend, but we need to free them here + // as well + delete draggedTab._dragOffsetX; + delete draggedTab._dragOffsetY; + ]]></handler> + + <handler event="dragend"><![CDATA[ + // Note: while this case is correctly handled here, this event + // isn't dispatched when the tab is moved within the tabstrip, + // see bug 460801. + + // * mozUserCancelled = the user pressed ESC to cancel the drag + var dt = event.dataTransfer; + if (dt.mozUserCancelled || dt.dropEffect != "none") + return; + + // Disable detach within the browser toolbox + var eX = event.screenX; + var eY = event.screenY; + var wX = window.screenX; + // check if the drop point is horizontally within the window + if (eX > wX && eX < (wX + window.outerWidth)) { + let bo = this.mTabstrip.boxObject; + // also avoid detaching if the the tab was dropped too close to + // the tabbar (half a tab) + let endScreenY = bo.screenY + 1.5 * bo.height; + if (eY < endScreenY && eY > window.screenY) + return; + } + + var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0); + // screen.availLeft et. al. only check the screen that this window is on, + // but we want to look at the screen the tab is being dropped onto. + var sX = {}, sY = {}, sWidth = {}, sHeight = {}; + Cc["@mozilla.org/gfx/screenmanager;1"] + .getService(Ci.nsIScreenManager) + .screenForRect(eX, eY, 1, 1) + .GetAvailRect(sX, sY, sWidth, sHeight); + // ensure new window entirely within screen + var winWidth = Math.min(window.outerWidth, sWidth.value); + var winHeight = Math.min(window.outerHeight, sHeight.value); + var left = Math.min(Math.max(eX - draggedTab._dragOffsetX, sX.value), + sX.value + sWidth.value - winWidth); + var top = Math.min(Math.max(eY - draggedTab._dragOffsetY, sY.value), + sY.value + sHeight.value - winHeight); + + delete draggedTab._dragOffsetX; + delete draggedTab._dragOffsetY; + + if (this.tabbrowser.tabs.length == 1) { + // resize _before_ move to ensure the window fits the new screen. if + // the window is too large for its screen, the window manager may do + // automatic repositioning. + window.resizeTo(winWidth, winHeight); + window.moveTo(left, top); + window.focus(); + } else { + this.tabbrowser.replaceTabWithWindow(draggedTab, { screenX: left, + screenY: top, +#ifndef XP_WIN + outerWidth: winWidth, + outerHeight: winHeight +#endif + }); + } + event.stopPropagation(); ]]></handler> <handler event="dragexit"><![CDATA[ this._dragTime = 0; // This does not work at all (see bug 458613) var target = event.relatedTarget; while (target && target != this) target = target.parentNode; if (target) return; this._tabDropIndicator.collapsed = true; + this._continueScroll(event); event.stopPropagation(); ]]></handler> </handlers> </binding> <!-- close-tab-button binding This binding relies on the structure of the tabbrowser binding. Therefore it should only be used as a child of the tab or the tabs @@ -4025,16 +3746,17 @@ <property name="hidden" readonly="true"> <getter> return this.getAttribute("hidden") == "true"; </getter> </property> <field name="mOverCloseButton">false</field> <field name="mCorrespondingMenuitem">null</field> + <field name="_fullyOpen">false</field> <field name="closing">false</field> </implementation> <handlers> <handler event="mouseover"> var anonid = event.originalTarget.getAttribute("anonid"); if (anonid == "close-button") this.mOverCloseButton = true;
--- a/browser/base/content/test/Makefile.in +++ b/browser/base/content/test/Makefile.in @@ -201,16 +201,17 @@ include $(topsrcdir)/config/rules.mk browser_selectTabAtIndex.js \ browser_tab_dragdrop.js \ browser_tab_dragdrop2.js \ browser_tab_dragdrop2_frame1.xul \ browser_tabfocus.js \ browser_tabs_isActive.js \ browser_tabs_owner.js \ browser_urlbarCopying.js \ + browser_urlbarEnter.js \ browser_urlbarTrimURLs.js \ browser_urlHighlight.js \ browser_visibleFindSelection.js \ browser_visibleTabs.js \ browser_visibleTabs_contextMenu.js \ browser_visibleTabs_bookmarkAllPages.js \ browser_visibleTabs_bookmarkAllTabs.js \ browser_visibleTabs_tabPreview.js \ @@ -240,16 +241,17 @@ include $(topsrcdir)/config/rules.mk browser_addon_bar_close_button.js \ browser_addon_bar_shortcut.js \ browser_addon_bar_aomlistener.js \ test_bug628179.html \ browser_wyciwyg_urlbarCopying.js \ test_wyciwyg_copying.html \ authenticate.sjs \ browser_minimize.js \ + browser_aboutSyncProgress.js \ browser_middleMouse_inherit.js \ $(NULL) ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT)) _BROWSER_FILES += \ browser_bug462289.js \ $(NULL) else
--- a/browser/base/content/test/browser_aboutHome.js +++ b/browser/base/content/test/browser_aboutHome.js @@ -5,16 +5,19 @@ registerCleanupFunction(function() { // Ensure we don't pollute prefs for next tests. try { Services.prefs.clearUserPref("network.cookies.cookieBehavior"); } catch (ex) {} try { Services.prefs.clearUserPref("network.cookie.lifetimePolicy"); } catch (ex) {} + try { + Services.prefs.clearUserPref("services.sync.username"); + } catch (ex) {} }); let gTests = [ { desc: "Check that rejecting cookies does not prevent page from working", setup: function () { @@ -109,16 +112,126 @@ let gTests = [ ok(snippetsElt, "Found snippets element"); is(snippetsElt.getElementsByTagName("span").length, 1, "A default snippet is visible."); executeSoon(runNextTest); } }, +{ + desc: "Check sync links visibility before and after Sync setup", + setup: function () + { + try { + Services.prefs.clearUserPref("services.sync.username"); + } catch (ex) {} + Services.obs.notifyObservers(null, "weave:service:ready", null); + }, + run: function () + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let pairLink = doc.getElementById("pairDeviceLink"); + let setupLink = doc.getElementById("setupSyncLink"); + + ok(pairLink, "Found 'Pair Device' link"); + ok(setupLink, "Found 'Set Up Sync' link"); + ok(!pairLink.hidden, "'Pair' link is visible before setup"); + ok(!setupLink.hidden, "'Set Up' link is visible before setup"); + + Services.obs.notifyObservers(null, "weave:service:setup-complete", null); + + executeSoon(function () { + setupLink = doc.getElementById("setupSyncLink"); + ok(setupLink.hidden, "'Set Up' link is hidden after setup"); + ok(!pairLink.hidden, "'Pair' link is visible after setup"); + + executeSoon(runNextTest); + }); + } +}, + +{ + desc: "Check sync links visibility before and after Sync unlink", + setup: function () + { + Services.prefs.setCharPref("services.sync.username", "someuser@domain.com"); + Services.obs.notifyObservers(null, "weave:service:ready", null); + }, + run: function () + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let pairLink = doc.getElementById("pairDeviceLink"); + let setupLink = doc.getElementById("setupSyncLink"); + + ok(!pairLink.hidden, "'Pair' link is visible before unlink"); + ok(setupLink.hidden, "'Set Up' link is hidden before unlink"); + + Services.obs.notifyObservers(null, "weave:service:start-over", null); + + executeSoon(function () { + setupLink = doc.getElementById("setupSyncLink"); + ok(!setupLink.hidden, "'Set Up' link is visible after unlink"); + ok(!pairLink.hidden, "'Pair' link is visible after unlink"); + executeSoon(runNextTest); + }); + } +}, + +{ + desc: "Check Pair Device link opens correct dialog with Sync account ", + setup: function () + { + Services.prefs.setCharPref("services.sync.username", "someuser@domain.com"); + Services.obs.notifyObservers(null, "weave:service:ready", null); + }, + run: function () + { + expectDialogWindow("Sync:AddDevice"); + let browser = gBrowser.selectedTab.linkedBrowser; + let button = browser.contentDocument.getElementById("pairDeviceLink"); + EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow); + } +}, + +{ + desc: "Check Pair Device link opens correct dialog without Sync account", + setup: function () + { + try { + Services.prefs.clearUserPref("services.sync.username"); + } catch (ex) {} + Services.obs.notifyObservers(null, "weave:service:ready", null); + }, + run: function () + { + expectDialogWindow("Weave:AccountSetup"); + let browser = gBrowser.selectedTab.linkedBrowser; + let button = browser.contentDocument.getElementById("pairDeviceLink"); + EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow); + } +}, + +{ + desc: "Check Sync Setup link opens correct dialog (without Sync account)", + setup: function () + { + try { + Services.prefs.clearUserPref("services.sync.username"); + } catch (ex) {} + Services.obs.notifyObservers(null, "weave:service:ready", null); + }, + run: function () + { + expectDialogWindow("Weave:AccountSetup"); + let browser = gBrowser.selectedTab.linkedBrowser; + let button = browser.contentDocument.getElementById("setupSyncLink"); + EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow); + } +}, ]; function test() { waitForExplicitFinish(); // browser-chrome test harness inits browser specifying an hardcoded page // and this causes nsIBrowserHandler.defaultArgs to not be evaluated since @@ -154,16 +267,32 @@ function runNextTest() executeSoon(test.run); }, true); } else { finish(); } } +function expectDialogWindow(expectedDialog) { + Services.ww.registerNotification(function onWindow(subject, topic) { + let win = subject.QueryInterface(Components.interfaces.nsIDOMWindow); + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad, false); + let wintype = win.document.documentElement.getAttribute("windowtype"); + if (topic == "domwindowopened" && wintype == expectedDialog) { + Services.ww.unregisterNotification(onWindow); + // Clean up dialog. + win.close(); + executeSoon(runNextTest); + } + }, false); + }); +} + function getStorage() { let aboutHomeURI = Services.io.newURI("moz-safe-about:home", null, null); let principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"]. getService(Components.interfaces.nsIScriptSecurityManager). getCodebasePrincipal(Services.io.newURI("about:home", null, null)); let dsm = Components.classes["@mozilla.org/dom/storagemanager;1"]. getService(Components.interfaces.nsIDOMStorageManager);
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/browser_aboutSyncProgress.js @@ -0,0 +1,101 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://services-sync/main.js"); + +let gTests = [ { + desc: "Makes sure the progress bar appears if firstSync pref is set", + setup: function () { + Services.prefs.setCharPref("services.sync.firstSync", "newAccount"); + }, + run: function () { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let progressBar = doc.getElementById("uploadProgressBar"); + + isnot(progressBar.style.display, "none", "progress bar should be visible"); + executeSoon(runNextTest); + } +}, + +{ + desc: "Makes sure the progress bar is hidden if firstSync pref is not set", + setup: function () { + Services.prefs.clearUserPref("services.sync.firstSync"); + is(Services.prefs.getPrefType("services.sync.firstSync"), + Ci.nsIPrefBranch.PREF_INVALID, "pref DNE" ); + }, + run: function () { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let progressBar = doc.getElementById("uploadProgressBar"); + + is(progressBar.style.display, "none", + "progress bar should not be visible"); + executeSoon(runNextTest); + } +}, +{ + desc: "Makes sure the observer updates are reflected in the progress bar", + setup: function () { + }, + run: function () { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let progressBar = doc.getElementById("uploadProgressBar"); + + Services.obs.notifyObservers(null, "weave:engine:sync:finish", null); + Services.obs.notifyObservers(null, "weave:engine:sync:error", null); + + let received = progressBar.getAttribute("value"); + + is(received, 2, "progress bar received correct notifications"); + executeSoon(runNextTest); + } +}, +{ + desc: "Close button should close tab", + setup: function (){ + }, + run: function () { + function onTabClosed() { + ok(true, "received TabClose notification"); + gBrowser.tabContainer.removeEventListener("TabClose", onTabClosed, false); + executeSoon(runNextTest); + } + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let button = doc.getElementById('closeButton'); + let window = doc.defaultView; + gBrowser.tabContainer.addEventListener("TabClose", onTabClosed, false); + EventUtils.sendMouseEvent({type: "click"}, button, window); + } +}, +]; + +function test () { + waitForExplicitFinish(); + executeSoon(runNextTest); +} + +function runNextTest() +{ + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } + + if (gTests.length) { + let test = gTests.shift(); + info(test.desc); + test.setup(); + let tab = gBrowser.selectedTab = gBrowser.addTab("about:sync-progress"); + tab.linkedBrowser.addEventListener("load", function (event) { + tab.linkedBrowser.removeEventListener("load", arguments.callee, true); + // Some part of the page is populated on load, so enqueue on it. + executeSoon(test.run); + }, true); + } + else { + finish(); + } +} +
--- a/browser/base/content/test/browser_bug553455.js +++ b/browser/base/content/test/browser_bug553455.js @@ -861,16 +861,17 @@ var XPInstallObserver = { } }; function test() { requestLongerTimeout(4); waitForExplicitFinish(); Services.prefs.setBoolPref("extensions.logging.enabled", true); + Services.prefs.setBoolPref("extensions.strictCompatibility", true); Services.obs.addObserver(XPInstallObserver, "addon-install-started", false); Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false); Services.obs.addObserver(XPInstallObserver, "addon-install-failed", false); Services.obs.addObserver(XPInstallObserver, "addon-install-complete", false); registerCleanupFunction(function() { // Make sure no more test parts run in case we were timed out @@ -879,16 +880,17 @@ function test() { AddonManager.getAllInstalls(function(aInstalls) { aInstalls.forEach(function(aInstall) { aInstall.cancel(); }); }); Services.prefs.clearUserPref("extensions.logging.enabled"); + Services.prefs.clearUserPref("extensions.strictCompatibility"); Services.obs.removeObserver(XPInstallObserver, "addon-install-started"); Services.obs.removeObserver(XPInstallObserver, "addon-install-blocked"); Services.obs.removeObserver(XPInstallObserver, "addon-install-failed"); Services.obs.removeObserver(XPInstallObserver, "addon-install-complete"); }); runNextTest();
--- a/browser/base/content/test/browser_popupUI.js +++ b/browser/base/content/test/browser_popupUI.js @@ -31,18 +31,18 @@ function findPopup() { } function testPopupUI(win) { var doc = win.document; ok(win.gURLBar, "location bar exists in the popup"); isnot(win.gURLBar.clientWidth, 0, "location bar is visible in the popup"); ok(win.gURLBar.readOnly, "location bar is read-only in the popup"); - is(doc.getElementById("Browser:OpenLocation").getAttribute("disabled"), "true", - "'open location' command is disabled in the popup"); + isnot(doc.getElementById("Browser:OpenLocation").getAttribute("disabled"), "true", + "'open location' command is not disabled in the popup"); let historyButton = doc.getAnonymousElementByAttribute(win.gURLBar, "anonid", "historydropmarker"); is(historyButton.clientWidth, 0, "history dropdown button is hidden in the popup"); EventUtils.synthesizeKey("t", { accelKey: true }, win); is(win.gBrowser.browsers.length, 1, "Accel+T doesn't open a new tab in the popup");
--- a/browser/base/content/test/browser_save_video.js +++ b/browser/base/content/test/browser_save_video.js @@ -1,17 +1,21 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.reset(); + /** * TestCase for bug 564387 * <https://bugzilla.mozilla.org/show_bug.cgi?id=564387> */ function test() { waitForExplicitFinish(); + var fileName; gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/bug564387.html"); registerCleanupFunction(function () { gBrowser.addTab(); gBrowser.removeCurrentTab(); }); @@ -30,60 +34,57 @@ function test() { }); }); function contextMenuOpened(event) { event.currentTarget.removeEventListener("popupshown", contextMenuOpened); // Create the folder the video will be saved into. var destDir = createTemporarySaveDirectory(); + var destFile = destDir.clone(); - mockFilePickerSettings.destDir = destDir; - mockFilePickerSettings.filterIndex = 1; // kSaveAsType_URL - mockFilePickerRegisterer.register(); + MockFilePicker.displayDirectory = destDir; + MockFilePicker.showCallback = function(fp) { + fileName = fp.defaultString; + destFile.append (fileName); + MockFilePicker.returnFiles = [destFile]; + MockFilePicker.filterIndex = 1; // kSaveAsType_URL + }; mockTransferCallback = onTransferComplete; mockTransferRegisterer.register(); registerCleanupFunction(function () { mockTransferRegisterer.unregister(); - mockFilePickerRegisterer.unregister(); + MockFilePicker.reset(); destDir.remove(true); }); // Select "Save Video As" option from context menu var saveVideoCommand = document.getElementById("context-savevideo"); saveVideoCommand.doCommand(); event.target.hidePopup(); } function onTransferComplete(downloadSuccess) { ok(downloadSuccess, "Video file should have been downloaded successfully"); - // Read the name of the saved file. - var fileName = mockFilePickerResults.selectedFile.leafName; - is(fileName, "Bug564387-expectedName.ogv", "Video file name is correctly retrieved from Content-Disposition http header"); finish(); } } Cc["@mozilla.org/moz/jssubscript-loader;1"] .getService(Ci.mozIJSSubScriptLoader) .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", this); -Cc["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Ci.mozIJSSubScriptLoader) - .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockFilePicker.js", - this); - function createTemporarySaveDirectory() { var saveDir = Cc["@mozilla.org/file/directory_service;1"] .getService(Ci.nsIProperties) .get("TmpD", Ci.nsIFile); saveDir.append("testsavedir"); if (!saveDir.exists()) saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); return saveDir;
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/browser_urlbarEnter.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_VALUE = "example.com/\xF7?\xF7"; +const START_VALUE = "example.com/%C3%B7?%C3%B7"; + +function test() { + waitForExplicitFinish(); + runNextTest(); +} + +function locationBarEnter(aEvent, aClosure) { + executeSoon(function() { + gURLBar.focus(); + EventUtils.synthesizeKey("VK_RETURN", aEvent); + addPageShowListener(aClosure); + }); +} + +function runNextTest() { + let test = gTests.shift(); + if (!test) { + finish(); + return; + } + + info("Running test: " + test.desc); + let tab = gBrowser.selectedTab = gBrowser.addTab(START_VALUE); + addPageShowListener(function() { + locationBarEnter(test.event, function() { + test.check(tab); + + // Clean up + while (gBrowser.tabs.length > 1) + gBrowser.removeTab(gBrowser.selectedTab) + runNextTest(); + }); + }); +} + +let gTests = [ + { desc: "Simple return keypress", + event: {}, + check: checkCurrent + }, + + { desc: "Alt+Return keypress", + event: { altKey: true }, + check: checkNewTab, + }, +] + +function checkCurrent(aTab) { + is(gURLBar.value, TEST_VALUE, "Urlbar should preserve the value on return keypress"); + is(gBrowser.selectedTab, aTab, "New URL was loaded in the current tab"); +} + +function checkNewTab(aTab) { + is(gURLBar.value, TEST_VALUE, "Urlbar should preserve the value on return keypress"); + isnot(gBrowser.selectedTab, aTab, "New URL was loaded in a new tab"); +} + +function addPageShowListener(aFunc) { + gBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() { + gBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false); + aFunc(); + }); +} +
--- a/browser/base/content/test/subtst_contextmenu.html +++ b/browser/base/content/test/subtst_contextmenu.html @@ -13,16 +13,18 @@ Browser context menu subtest. <img id="test-image" src="ctxmenu-image.png"> <canvas id="test-canvas" width="100" height="100" style="background-color: blue"></canvas> <video controls id="test-video-ok" src="video.ogg" width="100" height="100" style="background-color: green"></video> <video controls id="test-video-bad" src="bogus.duh" width="100" height="100" style="background-color: orange"></video> <video controls id="test-video-bad2" width="100" height="100" style="background-color: yellow"> <source src="bogus.duh" type="video/durrrr;"> </video> <iframe id="test-iframe" width="98" height="98" style="border: 1px solid black"></iframe> +<iframe id="test-video-in-iframe" src="video.ogg" width="98" height="98" style="border: 1px solid black"></iframe> +<iframe id="test-image-in-iframe" src="ctxmenu-image.png" width="98" height="98" style="border: 1px solid black"></iframe> <textarea id="test-textarea">chssseesbbbie</textarea> <!-- a weird word which generates only one suggestion --> <div id="test-contenteditable" contenteditable="true">chssseefsbbbie</div> <!-- a more weird word which generates no suggestions --> <input id="test-input-spellcheck" type="text" spellcheck="true" autofocus value="prodkjfgigrty"> <!-- this one also generates one suggestion --> <div contextmenu="myMenu"> <p id="test-pagemenu" hopeless="true">I've got a context menu!</p> <menu id="myMenu" type="context"> <menuitem label="Plain item" onclick="document.getElementById('test-pagemenu').removeAttribute('hopeless');"></menuitem> <menuitem label="Disabled item" disabled></menuitem>
--- a/browser/base/content/test/test_contextmenu.html +++ b/browser/base/content/test/test_contextmenu.html @@ -242,16 +242,22 @@ function checkMenu(menu, expectedItems, * (thus kicking off another cycle). * */ function runTest(testNum) { // Seems we need to enable this again, or sendKeyEvent() complaints. netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); ok(true, "Starting test #" + testNum); + var inspectItems = []; + if (SpecialPowers.getBoolPref("devtools.inspector.enabled")) { + inspectItems = ["---", null, + "context-inspect", true]; + } + switch (testNum) { case 1: // Invoke context menu for next test. openContextMenuFor(text); break; case 2: // Context menu for plain text @@ -263,89 +269,82 @@ function runTest(testNum) { "context-bookmarkpage", true, "context-savepage", true, "context-sendpage", true, "---", null, "context-viewbgimage", false, "context-selectall", true, "---", null, "context-viewsource", true, - "context-viewinfo", true, - "---", null, - "context-inspect", true]); + "context-viewinfo", true + ].concat(inspectItems)); closeContextMenu(); openContextMenuFor(link); // Invoke context menu for next test. break; case 3: // Context menu for text link checkContextMenu(["context-openlinkintab", true, "context-openlink", true, "---", null, "context-bookmarklink", true, "context-savelink", true, "context-sendlink", true, - "context-copylink", true, - "---", null, - "context-inspect", true]); + "context-copylink", true + ].concat(inspectItems)); closeContextMenu(); openContextMenuFor(mailto); // Invoke context menu for next test. break; case 4: // Context menu for text mailto-link - checkContextMenu(["context-copyemail", true, - "---", null, - "context-inspect", true]); + checkContextMenu(["context-copyemail", true].concat(inspectItems)); closeContextMenu(); openContextMenuFor(input); // Invoke context menu for next test. break; case 5: // Context menu for text input field checkContextMenu(["context-undo", false, "---", null, "context-cut", false, "context-copy", false, "context-paste", null, // ignore clipboard state "context-delete", false, "---", null, "context-selectall", false, "---", null, - "spell-check-enabled", true, - "---", null, - "context-inspect", true]); + "spell-check-enabled", true + ].concat(inspectItems)); closeContextMenu(); openContextMenuFor(img); // Invoke context menu for next test. break; case 6: // Context menu for an image checkContextMenu(["context-viewimage", true, "context-copyimage-contents", true, "context-copyimage", true, "---", null, "context-saveimage", true, "context-sendimage", true, "context-setDesktopBackground", true, - "context-viewimageinfo", true, - "---", null, - "context-inspect", true]); + "context-viewimageinfo", true + ].concat(inspectItems)); closeContextMenu(); openContextMenuFor(canvas); // Invoke context menu for next test. break; case 7: // Context menu for a canvas checkContextMenu(["context-viewimage", true, "context-saveimage", true, "context-bookmarkpage", true, - "context-selectall", true, - "---", null, - "context-inspect", true]); + "context-selectall", true + ].concat(inspectItems)); closeContextMenu(); openContextMenuFor(video_ok); // Invoke context menu for next test. break; case 8: // Context menu for a video (with a VALID media source) checkContextMenu(["context-media-play", true, "context-media-mute", true, @@ -353,19 +352,18 @@ function runTest(testNum) { "context-video-showstats", true, "context-video-fullscreen", true, "---", null, "context-viewvideo", true, "context-copyvideourl", true, "---", null, "context-savevideo", true, "context-video-saveimage", true, - "context-sendvideo", true, - "---", null, - "context-inspect", true]); + "context-sendvideo", true + ].concat(inspectItems)); closeContextMenu(); openContextMenuFor(video_bad); // Invoke context menu for next test. break; case 9: // Context menu for a video (with an INVALID media source) checkContextMenu(["context-media-play", false, "context-media-mute", false, @@ -373,19 +371,18 @@ function runTest(testNum) { "context-video-showstats", false, "context-video-fullscreen", false, "---", null, "context-viewvideo", true, "context-copyvideourl", true, "---", null, "context-savevideo", true, "context-video-saveimage", false, - "context-sendvideo", true, - "---", null, - "context-inspect", true]); + "context-sendvideo", true + ].concat(inspectItems)); closeContextMenu(); openContextMenuFor(video_bad2); // Invoke context menu for next test. break; case 10: // Context menu for a video (with an INVALID media source) checkContextMenu(["context-media-play", false, "context-media-mute", false, @@ -393,19 +390,18 @@ function runTest(testNum) { "context-video-showstats", false, "context-video-fullscreen", false, "---", null, "context-viewvideo", false, "context-copyvideourl", false, "---", null, "context-savevideo", false, "context-video-saveimage", false, - "context-sendvideo", false, - "---", null, - "context-inspect", true]); + "context-sendvideo", false + ].concat(inspectItems)); closeContextMenu(); openContextMenuFor(iframe); // Invoke context menu for next test. break; case 11: // Context menu for an iframe checkContextMenu(["context-back", false, "context-forward", false, @@ -429,24 +425,81 @@ function runTest(testNum) { "context-saveframe", true, "---", null, "context-printframe", true, "---", null, "context-viewframesource", true, "context-viewframeinfo", true], null, "---", null, "context-viewsource", true, - "context-viewinfo", true, - "---", null, - "context-inspect", true]); + "context-viewinfo", true + ].concat(inspectItems)); + closeContextMenu(); + openContextMenuFor(video_in_iframe); // Invoke context menu for next test. + break; + + case 12: + // Context menu for a video in an iframe + checkContextMenu(["context-media-play", true, + "context-media-mute", true, + "context-media-hidecontrols", true, + "context-video-showstats", true, + "context-video-fullscreen", true, + "---", null, + "context-viewvideo", true, + "context-copyvideourl", true, + "---", null, + "context-savevideo", true, + "context-video-saveimage", true, + "context-sendvideo", true, + "frame", null, + ["context-showonlythisframe", true, + "context-openframeintab", true, + "context-openframe", true, + "---", null, + "context-reloadframe", true, + "---", null, + "context-bookmarkframe", true, + "context-saveframe", true, + "---", null, + "context-printframe", true, + "---", null, + "context-viewframeinfo", true], null].concat(inspectItems)); + closeContextMenu(); + openContextMenuFor(image_in_iframe); // Invoke context menu for next test. + break; + + case 13: + // Context menu for an image in an iframe + checkContextMenu(["context-viewimage", true, + "context-copyimage-contents", true, + "context-copyimage", true, + "---", null, + "context-saveimage", true, + "context-sendimage", true, + "context-setDesktopBackground", true, + "context-viewimageinfo", true, + "frame", null, + ["context-showonlythisframe", true, + "context-openframeintab", true, + "context-openframe", true, + "---", null, + "context-reloadframe", true, + "---", null, + "context-bookmarkframe", true, + "context-saveframe", true, + "---", null, + "context-printframe", true, + "---", null, + "context-viewframeinfo", true], null].concat(inspectItems)); closeContextMenu(); openContextMenuFor(textarea, false, true); // Invoke context menu for next test, but wait for the spellcheck. break; - case 12: + case 14: // Context menu for textarea checkContextMenu(["*chubbiness", true, // spelling suggestion "spell-add-to-dictionary", true, "---", null, "context-undo", false, "---", null, "context-cut", false, "context-copy", false, @@ -455,24 +508,23 @@ function runTest(testNum) { "---", null, "context-selectall", true, "---", null, "spell-check-enabled", true, "spell-dictionaries", true, ["spell-check-dictionary-en-US", true, "---", null, "spell-add-dictionaries", true], null, - "---", null, - "context-inspect", true]); + ].concat(inspectItems)); closeContextMenu(); openContextMenuFor(contenteditable); // Invoke context menu for next test. break; - case 13: + case 15: // Context menu for contenteditable checkContextMenu(["spell-no-suggestions", false, "spell-add-to-dictionary", true, "---", null, "context-undo", false, "---", null, "context-cut", false, "context-copy", false, @@ -480,25 +532,24 @@ function runTest(testNum) { "context-delete", false, "---", null, "context-selectall", true, "---", null, "spell-check-enabled", true, "spell-dictionaries", true, ["spell-check-dictionary-en-US", true, "---", null, - "spell-add-dictionaries", true], null, - "---", null, - "context-inspect", true]); + "spell-add-dictionaries", true], null + ].concat(inspectItems)); closeContextMenu(); openContextMenuFor(inputspell); // Invoke context menu for next test. break; - case 14: + case 16: // Context menu for spell-check input checkContextMenu(["*prodigality", true, // spelling suggestion "spell-add-to-dictionary", true, "---", null, "context-undo", false, "---", null, "context-cut", false, "context-copy", false, @@ -506,31 +557,30 @@ function runTest(testNum) { "context-delete", false, "---", null, "context-selectall", true, "---", null, "spell-check-enabled", true, "spell-dictionaries", true, ["spell-check-dictionary-en-US", true, "---", null, - "spell-add-dictionaries", true], null, - "---", null, - "context-inspect", true]); + "spell-add-dictionaries", true], null + ].concat(inspectItems)); closeContextMenu(); openContextMenuFor(link); // Invoke context menu for next test. break; - case 15: + case 17: executeCopyCommand("cmd_copyLink", "http://mozilla.com/"); closeContextMenu(); openContextMenuFor(pagemenu); // Invoke context menu for next test. break; - case 16: + case 18: // Context menu for element with assigned content context menu checkContextMenu(["+Plain item", {type: "", icon: "", checked: false, disabled: false}, "+Disabled item", {type: "", icon: "", checked: false, disabled: true}, "+Item w/ textContent", {type: "", icon: "", checked: false, disabled: false}, "---", null, "+Checkbox", {type: "checkbox", icon: "", checked: true, disabled: false}, "---", null, "+Radio1", {type: "checkbox", icon: "", checked: true, disabled: false}, @@ -555,44 +605,42 @@ function runTest(testNum) { "context-bookmarkpage", true, "context-savepage", true, "context-sendpage", true, "---", null, "context-viewbgimage", false, "context-selectall", true, "---", null, "context-viewsource", true, - "context-viewinfo", true, - "---", null, - "context-inspect", true]); + "context-viewinfo", true + ].concat(inspectItems)); invokeItemAction("0"); closeContextMenu(); openContextMenuFor(pagemenu, true); // Invoke context menu for next test. break; - case 17: + case 19: // Context menu for element with assigned content context menu // The shift key should bypass content context menu processing checkContextMenu(["context-back", false, "context-forward", false, "context-reload", true, "context-stop", false, "---", null, "context-bookmarkpage", true, "context-savepage", true, "context-sendpage", true, "---", null, "context-viewbgimage", false, "context-selectall", true, "---", null, "context-viewsource", true, - "context-viewinfo", true, - "---", null, - "context-inspect", true]); + "context-viewinfo", true + ].concat(inspectItems)); subwindow.close(); SimpleTest.finish(); return; /* * Other things that would be nice to test: * - selected text @@ -609,17 +657,17 @@ function runTest(testNum) { } } var testNum = 1; var subwindow, chromeWin, contextMenu, lastElement; var text, link, mailto, input, img, canvas, video_ok, video_bad, video_bad2, - iframe, textarea, contenteditable, inputspell, pagemenu; + iframe, video_in_iframe, image_in_iframe, textarea, contenteditable, inputspell, pagemenu; function startTest() { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); chromeWin = subwindow .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem) .rootTreeItem @@ -641,16 +689,19 @@ function startTest() { mailto = subwindow.document.getElementById("test-mailto"); input = subwindow.document.getElementById("test-input"); img = subwindow.document.getElementById("test-image"); canvas = subwindow.document.getElementById("test-canvas"); video_ok = subwindow.document.getElementById("test-video-ok"); video_bad = subwindow.document.getElementById("test-video-bad"); video_bad2 = subwindow.document.getElementById("test-video-bad2"); iframe = subwindow.document.getElementById("test-iframe"); + video_in_iframe = subwindow.document.getElementById("test-video-in-iframe").contentDocument.getElementsByTagName("video")[0]; + video_in_iframe.pause(); + image_in_iframe = subwindow.document.getElementById("test-image-in-iframe").contentDocument.getElementsByTagName("img")[0]; textarea = subwindow.document.getElementById("test-textarea"); contenteditable = subwindow.document.getElementById("test-contenteditable"); contenteditable.focus(); // content editable needs to be focused to enable spellcheck inputspell = subwindow.document.getElementById("test-input-spellcheck"); pagemenu = subwindow.document.getElementById("test-pagemenu"); contextMenu.addEventListener("popupshown", function() { runTest(++testNum); }, false); runTest(1);
--- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -321,16 +321,20 @@ function loadCurrent() { let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from // inheriting the currently loaded document's principal, unless this // URL is marked as safe to inherit (e.g. came from a bookmark // keyword). if (!mayInheritPrincipal) flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; + // If the value wasn't typed, we know that we decoded the value as + // UTF-8 (see losslessDecodeURI) + if (!this.valueIsTyped) + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8; gBrowser.loadURIWithFlags(url, flags, null, null, postData); } // Focus the content area before triggering loads, since if the load // occurs in a new tab, we want focus to be restored to the content // area when the current tab is re-selected. gBrowser.selectedBrowser.focus(); @@ -356,16 +360,18 @@ if (where == "current") { loadCurrent(); } else { this.handleRevert(); let params = { allowThirdPartyFixup: true, postData: postData }; if (altEnter) params.inBackground = false; + if (!this.valueIsTyped) + params.isUTF8 = true; openUILinkIn(url, where, params); } } else { loadCurrent(); } ]]></body> </method>
--- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -37,16 +37,18 @@ # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** // Services = object with smart getters for common XPCOM services Components.utils.import("resource://gre/modules/Services.jsm"); +var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab"; + var gBidiUI = false; function getBrowserURL() { return "chrome://browser/content/browser.xul"; } function getTopWin(skipPopups) { @@ -192,16 +194,18 @@ function openLinkIn(url, where, params) var aFromChrome = params.fromChrome; var aAllowThirdPartyFixup = params.allowThirdPartyFixup; var aPostData = params.postData; var aCharset = params.charset; var aReferrerURI = params.referrerURI; var aRelatedToCurrent = params.relatedToCurrent; var aInBackground = params.inBackground; var aDisallowInheritPrincipal = params.disallowInheritPrincipal; + // Currently, this parameter works only for where=="tab" or "current" + var aIsUTF8 = params.isUTF8; if (where == "save") { saveURL(url, null, null, true, null, aReferrerURI); return; } const Cc = Components.classes; const Ci = Components.interfaces; @@ -265,30 +269,33 @@ function openLinkIn(url, where, params) switch (where) { case "current": let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; if (aAllowThirdPartyFixup) flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; if (aDisallowInheritPrincipal) flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; + if (aIsUTF8) + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8; w.gBrowser.loadURIWithFlags(url, flags, aReferrerURI, null, aPostData); break; case "tabshifted": loadInBackground = !loadInBackground; // fall through case "tab": let browser = w.gBrowser; browser.loadOneTab(url, { referrerURI: aReferrerURI, charset: aCharset, postData: aPostData, inBackground: loadInBackground, allowThirdPartyFixup: aAllowThirdPartyFixup, - relatedToCurrent: aRelatedToCurrent}); + relatedToCurrent: aRelatedToCurrent, + isUTF8: aIsUTF8}); break; } // If this window is active, focus the target window. Otherwise, focus the // content but don't raise the window, since the URI we just loaded may have // resulted in a new frontmost window (e.g. "javascript:window.open('');"). var fm = Components.classes["@mozilla.org/focus-manager;1"]. getService(Components.interfaces.nsIFocusManager);
--- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -66,16 +66,18 @@ browser.jar: content/browser/syncSetup.js (content/syncSetup.js) * content/browser/syncGenericChange.xul (content/syncGenericChange.xul) content/browser/syncGenericChange.js (content/syncGenericChange.js) * content/browser/syncKey.xhtml (content/syncKey.xhtml) * content/browser/syncNotification.xml (content/syncNotification.xml) * content/browser/syncQuota.xul (content/syncQuota.xul) content/browser/syncQuota.js (content/syncQuota.js) content/browser/syncUtils.js (content/syncUtils.js) + content/browser/syncProgress.js (content/syncProgress.js) +* content/browser/syncProgress.xhtml (content/syncProgress.xhtml) #endif # XXX: We should exclude this one as well (bug 71895) * content/browser/hiddenWindow.xul (content/hiddenWindow.xul) #ifdef XP_MACOSX * content/browser/macBrowserOverlay.xul (content/macBrowserOverlay.xul) * content/browser/downloadManagerOverlay.xul (content/downloadManagerOverlay.xul) * content/browser/jsConsoleOverlay.xul (content/jsConsoleOverlay.xul) * content/browser/softwareUpdateOverlay.xul (content/softwareUpdateOverlay.xul)
--- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -92,16 +92,18 @@ static RedirEntry kRedirMap[] = { nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT }, { "robots", "chrome://browser/content/aboutRobots.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT }, { "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml", nsIAboutModule::ALLOW_SCRIPT }, #ifdef MOZ_SERVICES_SYNC + { "sync-progress", "chrome://browser/content/syncProgress.xhtml", + nsIAboutModule::ALLOW_SCRIPT }, { "sync-tabs", "chrome://browser/content/aboutSyncTabs.xul", nsIAboutModule::ALLOW_SCRIPT }, #endif { "home", "chrome://browser/content/aboutHome.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT }, { "permissions", "chrome://browser/content/preferences/aboutPermissions.xul", nsIAboutModule::ALLOW_SCRIPT },
--- a/browser/components/build/nsModule.cpp +++ b/browser/components/build/nsModule.cpp @@ -151,16 +151,17 @@ static const mozilla::Module::ContractID { NS_ABOUT_MODULE_CONTRACTID_PREFIX "certerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "feeds", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "privatebrowsing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "rights", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "robots", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "sessionrestore", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, #ifdef MOZ_SERVICES_SYNC { NS_ABOUT_MODULE_CONTRACTID_PREFIX "sync-tabs", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "sync-progress", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, #endif { NS_ABOUT_MODULE_CONTRACTID_PREFIX "home", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "permissions", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_PROFILEMIGRATOR_CONTRACTID, &kNS_FIREFOX_PROFILEMIGRATOR_CID }, #if defined(XP_WIN) && !defined(__MINGW32__) { NS_BROWSERPROFILEMIGRATOR_CONTRACTID_PREFIX "ie", &kNS_WINIEPROFILEMIGRATOR_CID }, #elif defined(XP_MACOSX) { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
--- a/browser/components/dirprovider/Makefile.in +++ b/browser/components/dirprovider/Makefile.in @@ -40,17 +40,19 @@ topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk MODULE = browserdir LIBRARY_NAME = browserdir_s +ifdef ENABLE_TESTS DIRS = tests +endif FORCE_STATIC_LIB = 1 FORCE_USE_PIC = 1 # Because we are an application component, link against the CRT statically # (on Windows, but only if we're not building our own CRT for jemalloc) ifndef MOZ_MEMORY USE_STATIC_LIBS = 1
--- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -634,18 +634,18 @@ BrowserGlue.prototype = { } } ]; // Set pref to indicate we've shown the notification. var currentVersion = Services.prefs.getIntPref("browser.rights.version"); Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true); - var box = notifyBox.appendNotification(notifyRightsText, "about-rights", null, notifyBox.PRIORITY_INFO_LOW, buttons); - box.persistence = 3; // arbitrary number, just so bar sticks around for a bit + var notification = notifyBox.appendNotification(notifyRightsText, "about-rights", null, notifyBox.PRIORITY_INFO_LOW, buttons); + notification.persistence = -1; // Until user closes it }, _showUpdateNotification: function BG__showUpdateNotification() { Services.prefs.clearUserPref("app.update.postupdate"); var um = Cc["@mozilla.org/updates/update-manager;1"]. getService(Ci.nsIUpdateManager); try { @@ -704,20 +704,20 @@ BrowserGlue.prototype = { accessKey: key, popup: null, callback: function(aNotificationBar, aButton) { browser.selectedTab = browser.addTab(url); } } ]; - let box = notifyBox.appendNotification(text, "post-update-notification", - null, notifyBox.PRIORITY_INFO_LOW, - buttons); - box.persistence = 3; + let notification = notifyBox.appendNotification(text, "post-update-notification", + null, notifyBox.PRIORITY_INFO_LOW, + buttons); + notification.persistence = -1; // Until user closes it } if (actions.indexOf("showAlert") == -1) return; let notifier; try { notifier = Cc["@mozilla.org/alerts-service;1"]. @@ -756,16 +756,17 @@ BrowserGlue.prototype = { catch (e) { } }, #ifdef MOZ_TELEMETRY_REPORTING _showTelemetryNotification: function BG__showTelemetryNotification() { const PREF_TELEMETRY_PROMPTED = "toolkit.telemetry.prompted"; const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled"; + const PREF_TELEMETRY_REJECTED = "toolkit.telemetry.rejected"; const PREF_TELEMETRY_INFOURL = "toolkit.telemetry.infoURL"; const PREF_TELEMETRY_SERVER_OWNER = "toolkit.telemetry.server_owner"; // This is used to reprompt users when privacy message changes const TELEMETRY_PROMPT_REV = 2; var telemetryPrompted = null; try { telemetryPrompted = Services.prefs.getIntPref(PREF_TELEMETRY_PROMPTED); @@ -787,49 +788,53 @@ BrowserGlue.prototype = { var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties"); var productName = brandBundle.GetStringFromName("brandFullName"); var serverOwner = Services.prefs.getCharPref(PREF_TELEMETRY_SERVER_OWNER); var telemetryPrompt = browserBundle.formatStringFromName("telemetryPrompt", [productName, serverOwner], 2); var buttons = [ { - label: browserBundle.GetStringFromName("telemetryYesButtonLabel"), + label: browserBundle.GetStringFromName("telemetryYesButtonLabel2"), accessKey: browserBundle.GetStringFromName("telemetryYesButtonAccessKey"), popup: null, callback: function(aNotificationBar, aButton) { Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true); } }, { label: browserBundle.GetStringFromName("telemetryNoButtonLabel"), accessKey: browserBundle.GetStringFromName("telemetryNoButtonAccessKey"), popup: null, - callback: function(aNotificationBar, aButton) {} + callback: function(aNotificationBar, aButton) { + Services.prefs.setBoolPref(PREF_TELEMETRY_REJECTED, true); + } } ]; // Set pref to indicate we've shown the notification. Services.prefs.setIntPref(PREF_TELEMETRY_PROMPTED, TELEMETRY_PROMPT_REV); var notification = notifyBox.appendNotification(telemetryPrompt, "telemetry", null, notifyBox.PRIORITY_INFO_LOW, buttons); - notification.persistence = 6; // arbitrary number, just so bar sticks around for a bit + notification.setAttribute("hideclose", true); + notification.persistence = -1; // Until user closes it let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; let link = notification.ownerDocument.createElementNS(XULNS, "label"); link.className = "text-link telemetry-text-link"; link.setAttribute("value", browserBundle.GetStringFromName("telemetryLinkLabel")); link.addEventListener('click', function() { // Open the learn more url in a new tab browser.selectedTab = browser.addTab(Services.prefs.getCharPref(PREF_TELEMETRY_INFOURL)); // Remove the notification on which the user clicked notification.parentNode.removeNotification(notification, true); // Add a new notification to that tab, with no "Learn more" link - var notifyBox = browser.getNotificationBox(); - notifyBox.appendNotification(telemetryPrompt, "telemetry", null, notifyBox.PRIORITY_INFO_LOW, buttons); + notifyBox = browser.getNotificationBox(); + notification = notifyBox.appendNotification(telemetryPrompt, "telemetry", null, notifyBox.PRIORITY_INFO_LOW, buttons); + notification.persistence = -1; // Until user closes it }, false); let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText"); description.appendChild(link); }, #endif _showPluginUpdatePage: function BG__showPluginUpdatePage() { Services.prefs.setBoolPref(PREF_PLUGINS_NOTIFYUSER, false); @@ -1083,20 +1088,20 @@ BrowserGlue.prototype = { popup: null, callback: function(aNotificationBar, aButton) { browser.selectedTab = browser.addTab(url); } } ]; var notifyBox = browser.getNotificationBox(); - var box = notifyBox.appendNotification(text, title, null, - notifyBox.PRIORITY_CRITICAL_MEDIUM, - buttons); - box.persistence = -1; // Until user closes it + var notification = notifyBox.appendNotification(text, title, null, + notifyBox.PRIORITY_CRITICAL_MEDIUM, + buttons); + notification.persistence = -1; // Until user closes it }, _migrateUI: function BG__migrateUI() { const UI_VERSION = 5; const BROWSER_DOCURL = "chrome://browser/content/browser.xul#"; let currentUIVersion = 0; try { currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
--- a/browser/components/places/content/browserPlacesViews.js +++ b/browser/components/places/content/browserPlacesViews.js @@ -581,19 +581,16 @@ PlacesViewBase.prototype = { nodeHistoryDetailsChanged: function() { }, nodeTagsChanged: function() { }, nodeDateAddedChanged: function() { }, nodeLastModifiedChanged: function() { }, nodeKeywordChanged: function() { }, sortingChanged: function() { }, batching: function() { }, - // Replaced by containerStateChanged. - containerOpened: function() { }, - containerClosed: function() { }, nodeInserted: function PVB_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) { let parentElt = aParentPlacesNode._DOMElement; if (!parentElt) throw "aParentPlacesNode node must have _DOMElement set"; if (!parentElt._built)
--- a/browser/components/places/content/controller.js +++ b/browser/components/places/content/controller.js @@ -132,17 +132,16 @@ PlacesController.prototype = { this.cutNodes = []; }, terminate: function PC_terminate() { this._releaseClipboardOwnership(); }, supportsCommand: function PC_supportsCommand(aCommand) { - //LOG("supportsCommand: " + command); // Non-Places specific commands that we also support switch (aCommand) { case "cmd_undo": case "cmd_redo": case "cmd_cut": case "cmd_copy": case "cmd_paste": case "cmd_delete": @@ -773,27 +772,16 @@ PlacesController.prototype = { if (performed) { // Select the new item. let insertedNodeId = PlacesUtils.bookmarks .getIdForItemAt(ip.itemId, ip.index); this._view.selectItems([insertedNodeId], false); } }, - - /** - * Create a new Bookmark folder somewhere. Prompts the user for the name - * of the folder. - */ - newFolder: function PC_newFolder() { - Cu.reportError("PlacesController.newFolder is deprecated and will be \ - removed in a future release. Use newItem instead."); - this.newItem("folder"); - }, - /** * Create a new Bookmark separator somewhere. */ newSeparator: function PC_newSeparator() { var ip = this._view.insertionPoint; if (!ip) throw Cr.NS_ERROR_NOT_AVAILABLE; var txn = PlacesUIUtils.ptm.createSeparator(ip.itemId, ip.index); @@ -1398,23 +1386,27 @@ let PlacesControllerDragHelper = { // Check every dragged item. for (let i = 0; i < dropCount; i++) { let flavor = this.getFirstValidFlavor(dt.mozTypesAt(i)); if (!flavor) return false; // Urls can be dropped on any insertionpoint. // XXXmano: remember that this method is called for each dragover event! - // Thus we shouldn't use unwrapNodes here at all if possible. I think it - // would be OK to accept bogus data here (this is not in our control and - // will just case the actual drop to be a no-op) and only rule out valid + // Thus we shouldn't use unwrapNodes here at all if possible. + // I think it would be OK to accept bogus data here (e.g. text which was + // somehow wrapped as TAB_DROP_TYPE, this is not in our control, and + // will just case the actual drop to be a no-op), and only rule out valid // expected cases, which are either unsupported flavors, or items which // cannot be dropped in the current insertionpoint. The last case will // likely force us to use unwrapNodes for the private data types of // places. + if (flavor == TAB_DROP_TYPE) + continue; + let data = dt.mozGetDataAt(flavor, i); let dragged; try { dragged = PlacesUtils.unwrapNodes(data, flavor)[0]; } catch (e) { return false; } @@ -1518,18 +1510,31 @@ let PlacesControllerDragHelper = { let movedCount = 0; for (let i = 0; i < dropCount; ++i) { let flavor = this.getFirstValidFlavor(dt.mozTypesAt(i)); if (!flavor) return false; let data = dt.mozGetDataAt(flavor, i); let unwrapped; - // There's only ever one in the D&D case. - unwrapped = PlacesUtils.unwrapNodes(data, flavor)[0]; + if (flavor != TAB_DROP_TYPE) { + // There's only ever one in the D&D case. + unwrapped = PlacesUtils.unwrapNodes(data, flavor)[0]; + } + else if (data instanceof XULElement && data.localName == "tab" && + data.ownerDocument.defaultView instanceof ChromeWindow) { + let uri = data.linkedBrowser.currentURI; + let spec = uri ? uri.spec : "about:blank"; + let title = data.label; + unwrapped = { uri: spec, + title: data.label, + type: PlacesUtils.TYPE_X_MOZ_URL}; + } + else + throw("bogus data was passed as a tab") let index = insertionPoint.index; // Adjust insertion index to prevent reversal of dragged items. When you // drag multiple elts upward: need to increment index or each successive // elt will be inserted at the same index, each above the previous. let dragginUp = insertionPoint.itemId == unwrapped.parent && index < PlacesUtils.bookmarks.getItemIndex(unwrapped.id); @@ -1573,16 +1578,17 @@ let PlacesControllerDragHelper = { PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR, PlacesUtils.TYPE_X_MOZ_PLACE], // The order matters. GENERIC_VIEW_DROP_TYPES: [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER, PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR, PlacesUtils.TYPE_X_MOZ_PLACE, PlacesUtils.TYPE_X_MOZ_URL, + TAB_DROP_TYPE, PlacesUtils.TYPE_UNICODE], }; XPCOMUtils.defineLazyServiceGetter(PlacesControllerDragHelper, "dragService", "@mozilla.org/widget/dragservice;1", "nsIDragService");
--- a/browser/components/places/content/treeView.js +++ b/browser/components/places/content/treeView.js @@ -85,17 +85,18 @@ PlacesTreeView.prototype = { * This is called once both the result and the tree are set. */ _finishInit: function PTV__finishInit() { let selection = this.selection; if (selection) selection.selectEventsSuppressed = true; if (!this._rootNode.containerOpen) { - // This triggers containerOpened which then builds the visible section. + // This triggers containerStateChanged which then builds the visible + // section. this._rootNode.containerOpen = true; } else this.invalidateContainer(this._rootNode); // "Activate" the sorting column and update commands. this.sortingChanged(this._result.sortingMode); @@ -853,27 +854,21 @@ PlacesTreeView.prototype = { this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATEADDED); }, nodeLastModifiedChanged: function PTV_nodeLastModifiedChanged(aNode, aNewValue) { this._invalidateCellValue(aNode, this.COLUMN_TYPE_LASTMODIFIED); }, - containerOpened: function PTV_containerOpened(aNode) { + containerStateChanged: + function PTV_containerStateChanged(aNode, aOldState, aNewState) { this.invalidateContainer(aNode); }, - containerClosed: function PTV_containerClosed(aNode) { - this.invalidateContainer(aNode); - }, - - containerStateChanged: - function PTV_containerStateChanged(aNode, aOldState, aNewState) {}, - invalidateContainer: function PTV_invalidateContainer(aContainer) { NS_ASSERT(this._result, "Need to have a result to update"); if (!this._tree) return; let startReplacement, replaceCount; if (aContainer == this._rootNode) { startReplacement = 0;
--- a/browser/components/places/src/PlacesUIUtils.jsm +++ b/browser/components/places/src/PlacesUIUtils.jsm @@ -327,248 +327,16 @@ var PlacesUIUtils = { : data.uri; return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(data.uri), container, index, title); } } return null; }, - _reportDeprecatedAddBookmarkMethod: - function PUIU__reportDeprecatedAddBookmarkMethod() { - // Removes "PUIU_". - let oldFuncName = arguments.callee.caller.name.slice(5); - Cu.reportError(oldFuncName + " is deprecated and will be removed in a " + - "future release. Use showBookmarkDialog instead."); - }, - - /** - * This is here for compatibility reasons, use ShowBookmarkDialog instead. - */ - showAddBookmarkUI: function PUIU_showAddBookmarkUI( - aURI, aTitle, aDescription, aDefaultInsertionPoint, aShowPicker, - aLoadInSidebar, aKeyword, aPostData, aCharSet) { - this._reportDeprecatedAddBookmarkMethod(); - - var info = { - action: "add", - type: "bookmark" - }; - - if (aURI) - info.uri = aURI; - - // allow default empty title - if (typeof(aTitle) == "string") - info.title = aTitle; - - if (aDescription) - info.description = aDescription; - - if (aDefaultInsertionPoint) { - info.defaultInsertionPoint = aDefaultInsertionPoint; - if (!aShowPicker) - info.hiddenRows = ["folderPicker"]; - } - - if (aLoadInSidebar) - info.loadBookmarkInSidebar = true; - - if (typeof(aKeyword) == "string") { - info.keyword = aKeyword; - if (typeof(aPostData) == "string") - info.postData = aPostData; - if (typeof(aCharSet) == "string") - info.charSet = aCharSet; - } - - return this.showBookmarkDialog(info); - }, - - /** - * This is here for compatibility reasons, use ShowBookmarkDialog instead. - */ - showMinimalAddBookmarkUI: - function PUIU_showMinimalAddBookmarkUI( - aURI, aTitle, aDescription, aDefaultInsertionPoint, aShowPicker, - aLoadInSidebar, aKeyword, aPostData, aCharSet) { - this._reportDeprecatedAddBookmarkMethod(); - - var info = { - action: "add", - type: "bookmark", - hiddenRows: ["description"] - }; - if (aURI) - info.uri = aURI; - - // allow default empty title - if (typeof(aTitle) == "string") - info.title = aTitle; - - if (aDescription) - info.description = aDescription; - - if (aDefaultInsertionPoint) { - info.defaultInsertionPoint = aDefaultInsertionPoint; - if (!aShowPicker) - info.hiddenRows.push("folderPicker"); - } - - if (aLoadInSidebar) - info.loadBookmarkInSidebar = true; - else - info.hiddenRows = info.hiddenRows.concat(["location", "loadInSidebar"]); - - if (typeof(aKeyword) == "string") { - info.keyword = aKeyword; - // Hide the Tags field if we are adding a keyword. - info.hiddenRows.push("tags"); - // Keyword related params. - if (typeof(aPostData) == "string") - info.postData = aPostData; - if (typeof(aCharSet) == "string") - info.charSet = aCharSet; - } - else - info.hiddenRows.push("keyword"); - - return this.showBookmarkDialog(info, undefined, true); - }, - - /** - * This is here for compatibility reasons, use ShowBookmarkDialog instead. - */ - showAddLivemarkUI: function PUIU_showAddLivemarkURI(aFeedURI, - aSiteURI, - aTitle, - aDescription, - aDefaultInsertionPoint, - aShowPicker) { - this._reportDeprecatedAddBookmarkMethod(); - - var info = { - action: "add", - type: "livemark" - }; - - if (aFeedURI) - info.feedURI = aFeedURI; - if (aSiteURI) - info.siteURI = aSiteURI; - - // allow default empty title - if (typeof(aTitle) == "string") - info.title = aTitle; - - if (aDescription) - info.description = aDescription; - - if (aDefaultInsertionPoint) { - info.defaultInsertionPoint = aDefaultInsertionPoint; - if (!aShowPicker) - info.hiddenRows = ["folderPicker"]; - } - return this.showBookmarkDialog(info); - }, - - /** - * This is here for compatibility reasons, use ShowBookmarkDialog instead. - */ - showMinimalAddLivemarkUI: - function PUIU_showMinimalAddLivemarkURI( - aFeedURI, aSiteURI, aTitle, aDescription, aDefaultInsertionPoint, - aShowPicker) { - - this._reportDeprecatedAddBookmarkMethod(); - - var info = { - action: "add", - type: "livemark", - hiddenRows: ["feedLocation", "siteLocation", "description"] - }; - - if (aFeedURI) - info.feedURI = aFeedURI; - if (aSiteURI) - info.siteURI = aSiteURI; - - // allow default empty title - if (typeof(aTitle) == "string") - info.title = aTitle; - - if (aDescription) - info.description = aDescription; - - if (aDefaultInsertionPoint) { - info.defaultInsertionPoint = aDefaultInsertionPoint; - if (!aShowPicker) - info.hiddenRows.push("folderPicker"); - } - return this.showBookmarkDialog(info, undefined, true); - }, - - /** - * This is here for compatibility reasons, use ShowBookmarkDialog instead. - */ - showMinimalAddMultiBookmarkUI: function PUIU_showAddMultiBookmarkUI(aURIList) { - this._reportDeprecatedAddBookmarkMethod(); - - if (aURIList.length == 0) - throw("showAddMultiBookmarkUI expects a list of nsIURI objects"); - var info = { - action: "add", - type: "folder", - hiddenRows: ["description"], - URIList: aURIList - }; - return this.showBookmarkDialog(info, undefined, true); - }, - - /** - * This is here for compatibility reasons, use ShowBookmarkDialog instead. - */ - showItemProperties: function PUIU_showItemProperties(aItemId, aType, aReadOnly) { - this._reportDeprecatedAddBookmarkMethod(); - - var info = { - action: "edit", - type: aType, - itemId: aItemId, - readOnly: aReadOnly - }; - return this.showBookmarkDialog(info); - }, - - /** - * This is here for compatibility reasons, use ShowBookmarkDialog instead. - */ - showAddFolderUI: - function PUIU_showAddFolderUI(aTitle, aDefaultInsertionPoint, aShowPicker) { - this._reportDeprecatedAddBookmarkMethod(); - - var info = { - action: "add", - type: "folder", - hiddenRows: [] - }; - - // allow default empty title - if (typeof(aTitle) == "string") - info.title = aTitle; - - if (aDefaultInsertionPoint) { - info.defaultInsertionPoint = aDefaultInsertionPoint; - if (!aShowPicker) - info.hiddenRows.push("folderPicker"); - } - return this.showBookmarkDialog(info); - }, - - /** * Shows the bookmark dialog corresponding to the specified info. * * @param aInfo * Describes the item to be edited/added in the dialog. * See documentation at the top of bookmarkProperties.js * @param aWindow * Owner window for the new dialog.
--- a/browser/components/places/tests/unit/head_bookmarks.js +++ b/browser/components/places/tests/unit/head_bookmarks.js @@ -102,10 +102,10 @@ let (backup_date = new Date().toLocaleFo } // Smart bookmarks constants. const SMART_BOOKMARKS_VERSION = 2; const SMART_BOOKMARKS_ON_TOOLBAR = 1; const SMART_BOOKMARKS_ON_MENU = 3; // Takes in count the additional separator. // Default bookmarks constants. -const DEFAULT_BOOKMARKS_ON_TOOLBAR = 2; +const DEFAULT_BOOKMARKS_ON_TOOLBAR = 1; const DEFAULT_BOOKMARKS_ON_MENU = 1;
--- a/browser/components/places/tests/unit/test_browserGlue_prefs.js +++ b/browser/components/places/tests/unit/test_browserGlue_prefs.js @@ -200,17 +200,17 @@ let gTests = [ print("Simulate Places init"); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); // Check bookmarks.html has been restored. itemId = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, - SMART_BOOKMARKS_ON_TOOLBAR + 1); + SMART_BOOKMARKS_ON_TOOLBAR); do_check_true(itemId > 0); // Check preferences have been reverted. do_check_false(Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS)); run_next_test(); }, function test_restore_import() @@ -232,17 +232,17 @@ let gTests = [ print("Simulate Places init"); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); // Check bookmarks.html has been restored. itemId = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, - SMART_BOOKMARKS_ON_TOOLBAR + 1); + SMART_BOOKMARKS_ON_TOOLBAR); do_check_true(itemId > 0); // Check preferences have been reverted. do_check_false(Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS)); do_check_false(Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); run_next_test(); }
--- a/browser/components/sessionstore/src/nsSessionStartup.js +++ b/browser/components/sessionstore/src/nsSessionStartup.js @@ -172,16 +172,19 @@ SessionStartup.prototype = { // if all stored tabs are pinned. if (this.doRestore() && (!this._initialState.windows || !this._initialState.windows.every(function (win) win.tabs.every(function (tab) tab.pinned)))) Services.obs.addObserver(this, "domwindowopened", true); Services.obs.addObserver(this, "sessionstore-windows-restored", true); + + if (this._sessionType != Ci.nsISessionStartup.NO_SESSION) + Services.obs.addObserver(this, "browser:purge-session-history", true); }, /** * Handle notifications */ observe: function sss_observe(aSubject, aTopic, aData) { switch (aTopic) { case "app-startup": @@ -192,29 +195,35 @@ SessionStartup.prototype = { Services.obs.removeObserver(this, "final-ui-startup"); Services.obs.removeObserver(this, "quit-application"); this.init(); break; case "quit-application": // no reason for initializing at this point (cf. bug 409115) Services.obs.removeObserver(this, "final-ui-startup"); Services.obs.removeObserver(this, "quit-application"); + if (this._sessionType != Ci.nsISessionStartup.NO_SESSION) + Services.obs.removeObserver(this, "browser:purge-session-history"); break; case "domwindowopened": var window = aSubject; var self = this; window.addEventListener("load", function() { self._onWindowOpened(window); window.removeEventListener("load", arguments.callee, false); }, false); break; case "sessionstore-windows-restored": Services.obs.removeObserver(this, "sessionstore-windows-restored"); // free _initialState after nsSessionStore is done with it this._initialState = null; + break; + case "browser:purge-session-history": + Services.obs.removeObserver(this, "browser:purge-session-history"); + // reset all state on sanitization this._sessionType = Ci.nsISessionStartup.NO_SESSION; break; } }, /** * Removes the default arguments from the first browser window * (and removes the "domwindowopened" observer afterwards).
--- a/browser/components/sessionstore/src/nsSessionStore.js +++ b/browser/components/sessionstore/src/nsSessionStore.js @@ -135,20 +135,25 @@ Cu.import("resource://gre/modules/Servic // debug.js adds NS_ASSERT. cf. bug 669196 Cu.import("resource://gre/modules/debug.js"); XPCOMUtils.defineLazyGetter(this, "NetUtil", function() { Cu.import("resource://gre/modules/NetUtil.jsm"); return NetUtil; }); +XPCOMUtils.defineLazyGetter(this, "ScratchpadManager", function() { + Cu.import("resource:///modules/devtools/scratchpad-manager.jsm"); + return ScratchpadManager; +}); + XPCOMUtils.defineLazyServiceGetter(this, "CookieSvc", "@mozilla.org/cookiemanager;1", "nsICookieManager2"); -#ifdef MOZ_CRASH_REPORTER +#ifdef MOZ_CRASHREPORTER XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter", "@mozilla.org/xre/app-info;1", "nsICrashReporter"); #endif XPCOMUtils.defineLazyServiceGetter(this, "SecuritySvc", "@mozilla.org/scriptsecuritymanager;1", "nsIScriptSecurityManager"); function debug(aMsg) { @@ -1577,16 +1582,20 @@ SessionStoreService.prototype = { } // Merge closed windows from this session with ones from last session if (lastSessionState._closedWindows) { this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows); this._capClosedWindows(); } + if (lastSessionState.scratchpads) { + ScratchpadManager.restoreSession(lastSessionState.scratchpads); + } + // Set data that persists between sessions this._recentCrashes = lastSessionState.session && lastSessionState.session.recentCrashes || 0; this._sessionStartTime = lastSessionState.session && lastSessionState.session.startTime || this._sessionStartTime; this._lastSessionState = null; @@ -2249,27 +2258,43 @@ SessionStoreService.prototype = { * should we check the privacy level for https * @param aIsPinned * is the entry we're evaluating for a pinned tab; used only if * aCheckPrivacy */ _extractHostsForCookies: function sss__extractHostsForCookies(aEntry, aHosts, aCheckPrivacy, aIsPinned) { - // _host and _scheme may not be set (for about: urls for example), in which - // case testing _scheme will be sufficient. - if (/https?/.test(aEntry._scheme) && !aHosts[aEntry._host] && + let host = aEntry._host, + scheme = aEntry._scheme; + + // If host & scheme aren't defined, then we are likely here in the startup + // process via _splitCookiesFromWindow. In that case, we'll turn aEntry.url + // into an nsIURI and get host/scheme from that. This will throw for about: + // urls in which case we don't need to do anything. + if (!host && !scheme) { + try { + let uri = this._getURIFromString(aEntry.url); + host = uri.host; + scheme = uri.scheme; + } + catch(ex) { } + } + + // host and scheme may not be set (for about: urls for example), in which + // case testing scheme will be sufficient. + if (/https?/.test(scheme) && !aHosts[host] && (!aCheckPrivacy || - this._checkPrivacyLevel(aEntry._scheme == "https", aIsPinned))) { + this._checkPrivacyLevel(scheme == "https", aIsPinned))) { // By setting this to true or false, we can determine when looking at // the host in _updateCookies if we should check for privacy. - aHosts[aEntry._host] = aIsPinned; + aHosts[host] = aIsPinned; } - else if (aEntry._scheme == "file") { - aHosts[aEntry._host] = true; + else if (scheme == "file") { + aHosts[host] = true; } if (aEntry.children) { aEntry.children.forEach(function(entry) { this._extractHostsForCookies(entry, aHosts, aCheckPrivacy, aIsPinned); }, this); } }, @@ -2476,17 +2501,33 @@ SessionStoreService.prototype = { this.activeWindowSSiCache = activeWindow.__SSi || ""; } ix = windows.indexOf(this.activeWindowSSiCache); // We don't want to restore focus to a minimized window or a window which had all its // tabs stripped out (doesn't exist). if (ix != -1 && total[ix] && total[ix].sizemode == "minimized") ix = -1; - return { windows: total, selectedWindow: ix + 1, _closedWindows: lastClosedWindowsCopy }; + let session = { + state: this._loadState == STATE_RUNNING ? STATE_RUNNING_STR : STATE_STOPPED_STR, + lastUpdate: Date.now(), + startTime: this._sessionStartTime, + recentCrashes: this._recentCrashes + }; + + // get open Scratchpad window states too + var scratchpads = ScratchpadManager.getSessionState(); + + return { + windows: total, + selectedWindow: ix + 1, + _closedWindows: lastClosedWindowsCopy, + session: session, + scratchpads: scratchpads + }; }, /** * serialize session data for a window * @param aWindow * Window reference * @returns string */ @@ -2560,17 +2601,17 @@ SessionStoreService.prototype = { // We're not returning from this before we end up calling restoreHistoryPrecursor // for this window, so make sure we send the SSWindowStateBusy event. this._setWindowStateBusy(aWindow); if (root._closedWindows) this._closedWindows = root._closedWindows; var winData; - if (!root.selectedWindow) { + if (!root.selectedWindow || root.selectedWindow > root.windows.length) { root.selectedWindow = 0; } else { // put the selected window at the beginning of the array to ensure that // it gets restored first root.windows.unshift(root.windows.splice(root.selectedWindow - 1, 1)[0]); } // open new windows for all further window entries of a multi-window session // (unless they don't contain any tab data) @@ -2683,16 +2724,20 @@ SessionStoreService.prototype = { } if (aOverwriteTabs || root._firstTabs) { this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs || []; } this.restoreHistoryPrecursor(aWindow, tabs, winData.tabs, (aOverwriteTabs ? (parseInt(winData.selected) || 1) : 0), 0, 0); + if (aState.scratchpads) { + ScratchpadManager.restoreSession(aState.scratchpads); + } + // This will force the keypress listener that Panorama has to attach if it // isn't already. This will be the case if tab view wasn't entered or there // were only visible tabs when TabView.init was first called. aWindow.TabView.init(); // set smoothScroll back to the original value tabstrip.smoothScroll = smoothScroll; @@ -3175,27 +3220,30 @@ SessionStoreService.prototype = { if (aEntry.postdata_b64) { var postdata = atob(aEntry.postdata_b64); var stream = Cc["@mozilla.org/io/string-input-stream;1"]. createInstance(Ci.nsIStringInputStream); stream.setData(postdata, postdata.length); shEntry.postData = stream; } + let childDocIdents = {}; if (aEntry.docIdentifier) { // If we have a serialized document identifier, try to find an SHEntry // which matches that doc identifier and adopt that SHEntry's // BFCacheEntry. If we don't find a match, insert shEntry as the match // for the document identifier. let matchingEntry = aDocIdentMap[aEntry.docIdentifier]; if (!matchingEntry) { - aDocIdentMap[aEntry.docIdentifier] = shEntry; + matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents}; + aDocIdentMap[aEntry.docIdentifier] = matchingEntry; } else { - shEntry.adoptBFCacheEntry(matchingEntry); + shEntry.adoptBFCacheEntry(matchingEntry.shEntry); + childDocIdents = matchingEntry.childDocIdents; } } if (aEntry.owner_b64) { var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"]. createInstance(Ci.nsIStringInputStream); var binaryData = atob(aEntry.owner_b64); ownerInput.setData(binaryData, binaryData.length); @@ -3207,18 +3255,34 @@ SessionStoreService.prototype = { } catch (ex) { debug(ex); } } if (aEntry.children && shEntry instanceof Ci.nsISHContainer) { for (var i = 0; i < aEntry.children.length; i++) { //XXXzpao Wallpaper patch for bug 514751 if (!aEntry.children[i].url) continue; + + // We're getting sessionrestore.js files with a cycle in the + // doc-identifier graph, likely due to bug 698656. (That is, we have + // an entry where doc identifier A is an ancestor of doc identifier B, + // and another entry where doc identifier B is an ancestor of A.) + // + // If we were to respect these doc identifiers, we'd create a cycle in + // the SHEntries themselves, which causes the docshell to loop forever + // when it looks for the root SHEntry. + // + // So as a hack to fix this, we restrict the scope of a doc identifier + // to be a node's siblings and cousins, and pass childDocIdents, not + // aDocIdents, to _deserializeHistoryEntry. That is, we say that two + // SHEntries with the same doc identifier have the same document iff + // they have the same parent or their parents have the same document. + shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap, - aDocIdentMap), i); + childDocIdents), i); } } return shEntry; }, /** * restores all sessionStorage "super cookies" @@ -3516,24 +3580,16 @@ SessionStoreService.prototype = { this._resume_session_once_on_shutdown = this._prefBranch.getBoolPref("sessionstore.resume_session_once"); this._prefBranch.setBoolPref("sessionstore.resume_session_once", true); // flush the preference file so preference will be saved in case of a crash Services.prefs.savePrefFile(null); } } - oState.session = { - state: this._loadState == STATE_RUNNING ? STATE_RUNNING_STR : STATE_STOPPED_STR, - lastUpdate: Date.now(), - startTime: this._sessionStartTime - }; - if (this._recentCrashes) - oState.session.recentCrashes = this._recentCrashes; - // Persist the last session if we deferred restoring it if (this._lastSessionState) oState.lastSessionState = this._lastSessionState; this._saveStateObject(oState); }, /** @@ -3791,17 +3847,17 @@ SessionStoreService.prototype = { _getURIFromString: function sss_getURIFromString(aString) { return Services.io.newURI(aString, null, null); }, /** * Annotate a breakpad crash report with the currently selected tab's URL. */ _updateCrashReportURL: function sss_updateCrashReportURL(aWindow) { -#ifdef MOZ_CRASH_REPORTER +#ifdef MOZ_CRASHREPORTER try { var currentURI = aWindow.gBrowser.currentURI.clone(); // if the current URI contains a username/password, remove it try { currentURI.userPass = ""; } catch (ex) { } // ignore failures on about: URIs @@ -3977,16 +4033,19 @@ SessionStoreService.prototype = { tab.entries.forEach(function(entry) { this._extractHostsForCookies(entry, cookieHosts, false) }, this); }, this); // By creating a regex we reduce overhead and there is only one loop pass // through either array (cookieHosts and aWinState.cookies). let hosts = Object.keys(cookieHosts).join("|").replace("\\.", "\\.", "g"); + // If we don't actually have any hosts, then we don't want to do anything. + if (!hosts.length) + return; let cookieRegex = new RegExp(".*(" + hosts + ")"); for (let cIndex = 0; cIndex < aWinState.cookies.length;) { if (cookieRegex.test(aWinState.cookies[cIndex].host)) { aTargetWinState.cookies = aTargetWinState.cookies.concat(aWinState.cookies.splice(cIndex, 1)); continue; } cIndex++;
--- a/browser/components/sessionstore/test/browser/Makefile.in +++ b/browser/components/sessionstore/test/browser/Makefile.in @@ -144,20 +144,25 @@ include $(topsrcdir)/config/rules.mk browser_615394-SSWindowState_events.js \ browser_618151.js \ browser_623779.js \ browser_624727.js \ browser_625257.js \ browser_628270.js \ browser_635418.js \ browser_636279.js \ + browser_644409-scratchpads.js \ browser_645428.js \ browser_659591.js \ browser_662812.js \ + browser_665702-state_session.js \ browser_682507.js \ + browser_687710.js \ + browser_687710_2.js \ + browser_694378.js \ $(NULL) ifneq ($(OS_ARCH),Darwin) _BROWSER_TEST_FILES += \ browser_597071.js \ browser_625016.js \ $(NULL) endif
--- a/browser/components/sessionstore/test/browser/browser_607016.js +++ b/browser/components/sessionstore/test/browser/browser_607016.js @@ -79,26 +79,28 @@ function test() { function progressCallback(aBrowser) { // We'll remove the progress listener after the first one because we aren't // loading any other tabs window.gBrowser.removeTabsProgressListener(progressListener); let curState = JSON.parse(ss.getBrowserState()); for (let i = 0; i < curState.windows[0].tabs.length; i++) { - if (state.windows[0].tabs[i].extData) { - is(curState.windows[0].tabs[i].extData["uniq"], - state.windows[0].tabs[i].extData["uniq"], + let tabState = state.windows[0].tabs[i]; + let tabCurState = curState.windows[0].tabs[i]; + if (tabState.extData) { + is(tabCurState.extData["uniq"], tabState.extData["uniq"], "sanity check that tab has correct extData"); } else { - ok(!("extData" in curState.windows[0].tabs[i]), - "sanity check that tab doesn't have extData"); - //XXXzpao output the tab state to help debug bug 679590 - info("tabState: " + JSON.stringify(curState.windows[0].tabs[i])); + // We aren't expecting there to be any data on extData, but panorama + // may be setting something, so we need to make sure that if we do have + // data, we just don't have anything for "uniq". + ok(!("extData" in tabCurState) || !("uniq" in tabCurState.extData), + "sanity check that tab doesn't have extData or extData doesn't have 'uniq'"); } } // Now we'll set a new unique value on 1 of the tabs let newUniq = r(); ss.setTabValue(gBrowser.tabs[1], "uniq", newUniq); gBrowser.removeTab(gBrowser.tabs[1]); let closedTabData = (JSON.parse(ss.getClosedTabData(window)))[0];
new file mode 100644 --- /dev/null +++ b/browser/components/sessionstore/test/browser/browser_644409-scratchpads.js @@ -0,0 +1,59 @@ + /* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const testState = { + windows: [{ + tabs: [ + { entries: [{ url: "about:blank" }] }, + ] + }], + scratchpads: [ + { text: "text1", executionContext: 1 }, + { text: "", executionContext: 2, filename: "test.js" } + ] +}; + +// only finish() when correct number of windows opened +var restored = []; +function addState(state) { + restored.push(state); + + if (restored.length == testState.scratchpads.length) { + ok(statesMatch(restored, testState.scratchpads), + "Two scratchpad windows restored"); + + Services.ww.unregisterNotification(windowObserver); + finish(); + } +} + +function test() { + waitForExplicitFinish(); + + Services.ww.registerNotification(windowObserver); + + ss.setBrowserState(JSON.stringify(testState)); +} + +function windowObserver(aSubject, aTopic, aData) { + if (aTopic == "domwindowopened") { + let win = aSubject.QueryInterface(Ci.nsIDOMWindow); + win.addEventListener("load", function() { + if (win.Scratchpad) { + let state = win.Scratchpad.getState(); + win.close(); + addState(state); + } + }, false); + } +} + +function statesMatch(restored, states) { + return states.every(function(state) { + return restored.some(function(restoredState) { + return state.filename == restoredState.filename && + state.text == restoredState.text && + state.executionContext == restoredState.executionContext; + }) + }); +} \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/browser/components/sessionstore/test/browser/browser_665702-state_session.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function compareArray(a, b) { + if (a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; +} + +function test() { + let currentState = JSON.parse(ss.getBrowserState()); + ok(currentState.session, "session data returned by getBrowserState"); + + let keys = Object.keys(currentState.session); + let expectedKeys = ["state", "lastUpdate", "startTime", "recentCrashes"]; + ok(compareArray(keys.sort(), expectedKeys.sort()), + "session object from getBrowserState has correct keys"); +}
new file mode 100644 --- /dev/null +++ b/browser/components/sessionstore/test/browser/browser_687710.js @@ -0,0 +1,44 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that sessionrestore handles cycles in the shentry graph properly. +// +// These cycles shouldn't be there in the first place, but they cause hangs +// when they mysteriously appear (bug 687710). Docshell code assumes this +// graph is a tree and tires to walk to the root. But if there's a cycle, +// there is no root, and we loop forever. + +let stateBackup = ss.getBrowserState(); + +let state = {windows:[{tabs:[{entries:[ + { + docIdentifier: 1, + url: "http://example.com", + children: [ + { + docIdentifier: 2, + url: "http://example.com" + } + ] + }, + { + docIdentifier: 2, + url: "http://example.com", + children: [ + { + docIdentifier: 1, + url: "http://example.com" + } + ] + } +]}]}]} + +function test() { + registerCleanupFunction(function () { + ss.setBrowserState(stateBackup); + }); + + /* This test fails by hanging. */ + ss.setBrowserState(JSON.stringify(state)); + ok(true, "Didn't hang!"); +}
new file mode 100644 --- /dev/null +++ b/browser/components/sessionstore/test/browser/browser_687710_2.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that the fix for bug 687710 isn't too aggressive -- shentries which are +// cousins should be able to share bfcache entries. + +let stateBackup = ss.getBrowserState(); + +let state = {entries:[ + { + docIdentifier: 1, + url: "http://example.com?1", + children: [{ docIdentifier: 10, + url: "http://example.com?10" }] + }, + { + docIdentifier: 1, + url: "http://example.com?1#a", + children: [{ docIdentifier: 10, + url: "http://example.com?10#aa" }] + } +]}; + +function test() +{ + registerCleanupFunction(function () { + ss.setBrowserState(stateBackup); + }); + + let tab = gBrowser.addTab("about:blank"); + ss.setTabState(tab, JSON.stringify(state)); + let history = tab.linkedBrowser.webNavigation.sessionHistory; + + is(history.count, 2, "history.count"); + for (let i = 0; i < history.count; i++) { + for (let j = 0; j < history.count; j++) { + compareEntries(i, j, history); + } + } +} + +function compareEntries(i, j, history) +{ + let e1 = history.getEntryAtIndex(i, false) + .QueryInterface(Ci.nsISHEntry) + .QueryInterface(Ci.nsISHContainer); + + let e2 = history.getEntryAtIndex(j, false) + .QueryInterface(Ci.nsISHEntry) + .QueryInterface(Ci.nsISHContainer); + + ok(e1.sharesDocumentWith(e2), + i + ' should share doc with ' + j); + is(e1.childCount, e2.childCount, + 'Child count mismatch (' + i + ', ' + j + ')'); + + for (let c = 0; c < e1.childCount; c++) { + let c1 = e1.GetChildAt(c); + let c2 = e2.GetChildAt(c); + + ok(c1.sharesDocumentWith(c2), + 'Cousins should share documents. (' + i + ', ' + j + ', ' + c + ')'); + } +}
new file mode 100644 --- /dev/null +++ b/browser/components/sessionstore/test/browser/browser_694378.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test Summary: +// 1. call ss.setWindowState with a broken state +// 1a. ensure that it doesn't throw. + +function test() { + waitForExplicitFinish(); + + let brokenState = { + windows: [ + { tabs: [{ entries: [{ url: "about:mozilla" }] }] } + //{ tabs: [{ entries: [{ url: "about:robots" }] }] }, + ], + selectedWindow: 2 + }; + let brokenStateString = JSON.stringify(brokenState); + + let gotError = false; + try { + ss.setWindowState(window, brokenStateString, true); + } + catch (ex) { + gotError = true; + info(ex); + } + + ok(!gotError, "ss.setWindowState did not throw an error"); + + // Make sure that we reset the state. Use a full state just in case things get crazy. + let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank" }] }]}]}; + waitForBrowserState(blankState, finish); +} +
--- a/browser/components/shell/src/nsWindowsShellService.cpp +++ b/browser/components/shell/src/nsWindowsShellService.cpp @@ -257,16 +257,19 @@ LaunchHelper(nsAutoString& aPath) return NS_OK; } NS_IMETHODIMP nsWindowsShellService::ShortcutMaintenance() { nsresult rv; + // XXX App ids were updated to a constant install path hash, + // XXX this code can be removed after a few upgrade cycles. + // Launch helper.exe so it can update the application user model ids on // shortcuts in the user's taskbar and start menu. This keeps older pinned // shortcuts grouped correctly after major updates. Note, we also do this // through the upgrade installer script, however, this is the only place we // have a chance to trap links created by users who do control the install/ // update process of the browser. nsCOMPtr<nsIWinTaskbar> taskbarInfo =
--- a/browser/components/tabview/tabitems.js +++ b/browser/components/tabview/tabitems.js @@ -147,17 +147,17 @@ function TabItem(tab, options) { }); this.droppable(true); TabItems.register(this); // ___ reconnect to data from Storage if (!TabItems.reconnectingPaused()) - this._reconnect(); + this._reconnect(options); }; TabItem.prototype = Utils.extend(new Item(), new Subscribable(), { // ---------- // Function: toString // Prints [TabItem (tab)] for debug use toString: function TabItem_toString() { return "[TabItem (" + this.tab + ")]"; @@ -334,52 +334,64 @@ TabItem.prototype = Utils.extend(new Ite this._saveThumbnailDelayed = {url: url, timeout: timeout}; } }, // ---------- // Function: _reconnect // Load the reciever's persistent data from storage. If there is none, // treats it as a new tab. - _reconnect: function TabItem__reconnect() { + // + // Parameters: + // options - an object with additional parameters, see below + // + // Possible options: + // groupItemId - if the tab doesn't have any data associated with it and + // groupItemId is available, add the tab to that group. + _reconnect: function TabItem__reconnect(options) { Utils.assertThrow(!this._reconnected, "shouldn't already be reconnected"); Utils.assertThrow(this.tab, "should have a xul:tab"); let tabData = Storage.getTabData(this.tab); + let groupItem; if (tabData && TabItems.storageSanity(tabData)) { this.loadThumbnail(tabData); if (this.parent) this.parent.remove(this, {immediately: true}); - let groupItem; - - if (tabData.groupID) { + if (tabData.groupID) groupItem = GroupItems.groupItem(tabData.groupID); - } else { + else groupItem = new GroupItem([], {immediately: true, bounds: tabData.bounds}); - } if (groupItem) { groupItem.add(this, {immediately: true}); // restore the active tab for each group between browser sessions if (tabData.active) groupItem.setActiveTab(this); // if it matches the selected tab or no active tab and the browser // tab is hidden, the active group item would be set. if (this.tab == gBrowser.selectedTab || (!GroupItems.getActiveGroupItem() && !this.tab.hidden)) UI.setActive(this.parent); } } else { - // create tab group by double click is handled in UI_init(). - GroupItems.newTab(this, {immediately: true}); + if (options && options.groupItemId) + groupItem = GroupItems.groupItem(options.groupItemId); + + if (groupItem) { + groupItem.add(this, {immediately: true}); + } else { + // create tab group by double click is handled in UI_init(). + GroupItems.newTab(this, {immediately: true}); + } } this._reconnected = true; this.save(); this._sendToSubscribers("reconnected"); }, // ---------- @@ -829,22 +841,31 @@ let TabItems = { // XXX bug #635975 - don't unlink the tab if the dom window is closing. if (!tab.pinned && !UI.isDOMWindowClosing) self.unlink(tab); } for (let name in this._eventListeners) { AllTabs.register(name, this._eventListeners[name]); } + let activeGroupItem = GroupItems.getActiveGroupItem(); + let activeGroupItemId = activeGroupItem ? activeGroupItem.id : null; // For each tab, create the link. AllTabs.tabs.forEach(function (tab) { if (tab.pinned) return; - self.link(tab, {immediately: true}); + let options = {immediately: true}; + // if tab is visible in the tabstrip and doesn't have any data stored in + // the session store (see TabItem__reconnect), it implies that it is a + // new tab which is created before Panorama is initialized. Therefore, + // passing the active group id to the link() method for setting it up. + if (!tab.hidden && activeGroupItemId) + options.groupItemId = activeGroupItemId; + self.link(tab, options); self.update(tab); }); }, // ---------- // Function: uninit uninit: function TabItems_uninit() { for (let name in this._eventListeners) {
--- a/browser/components/tabview/test/Makefile.in +++ b/browser/components/tabview/test/Makefile.in @@ -158,16 +158,17 @@ include $(topsrcdir)/config/rules.mk browser_tabview_bug673196.js \ browser_tabview_bug673729.js \ browser_tabview_bug677310.js \ browser_tabview_bug679853.js \ browser_tabview_bug681599.js \ browser_tabview_bug685476.js \ browser_tabview_bug685692.js \ browser_tabview_bug686654.js \ + browser_tabview_bug697390.js \ browser_tabview_click_group.js \ browser_tabview_dragdrop.js \ browser_tabview_exit_button.js \ browser_tabview_expander.js \ browser_tabview_firstrun_pref.js \ browser_tabview_group.js \ browser_tabview_launch.js \ browser_tabview_multiwindow_search.js \
new file mode 100644 --- /dev/null +++ b/browser/components/tabview/test/browser_tabview_bug697390.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let state = { + windows: [{ + tabs: [{ + entries: [{ url: "about:blank" }], + hidden: true, + extData: {"tabview-tab": '{"url":"about:blank","groupID":1,"bounds":{"left":120,"top":20,"width":20,"height":20}}'} + },{ + entries: [{ url: "about:blank" }], + hidden: false, + extData: {"tabview-tab": '{"url":"about:blank","groupID":2,"bounds":{"left":20,"top":20,"width":20,"height":20}}'}, + }], + selected: 2, + extData: { + "tabview-groups": '{"nextID":3,"activeGroupId":2, "totalNumber":2}', + "tabview-group": + '{"1":{"bounds":{"left":15,"top":5,"width":280,"height":232},"id":1},' + + '"2":{"bounds":{"left":309,"top":5,"width":267,"height":226},"id":2}}' + } + }] +}; + +function test() { + waitForExplicitFinish(); + + newWindowWithState(state, function(win) { + registerCleanupFunction(function() win.close()); + + win.gBrowser.addTab(); + + ok(win.gBrowser.tabs[0].hidden, "The first tab is hidden"); + win.gBrowser.selectedTab = win.gBrowser.tabs[0]; + + function onTabViewFrameInitialized() { + win.removeEventListener( + "tabviewframeinitialized", onTabViewFrameInitialized, false); + + let cw = win.TabView.getContentWindow(); + is(cw.GroupItems.groupItem(1).getChild(0).tab, win.gBrowser.selectedTab, "The tab in group one matches the selected tab"); + is(cw.GroupItems.groupItem(1).getChildren().length, 1, "The group one has only one tab item"); + is(cw.GroupItems.groupItem(2).getChildren().length, 2, "The group one has only two tab item") + + finish(); + } + win.addEventListener( + "tabviewframeinitialized", onTabViewFrameInitialized, false); + }); +} +
--- a/browser/config/mozconfigs/macosx32/debug +++ b/browser/config/mozconfigs/macosx32/debug @@ -1,12 +1,9 @@ -# Don't use the standard mozconfig. We don't want universal for a debug build. -#. $topsrcdir/build/macosx/universal/mozconfig - -ac_add_options --with-macos-sdk=/Developer/SDKs/MacOSX10.5.sdk +. $topsrcdir/build/macosx/mozconfig.leopard ac_add_options --enable-debug ac_add_options --enable-trace-malloc # Enable parallel compiling mk_add_options MOZ_MAKE_FLAGS="-j4" # Needed to enable breakpad in application.ini export MOZILLA_OFFICIAL=1
--- a/browser/devtools/Makefile.in +++ b/browser/devtools/Makefile.in @@ -46,16 +46,13 @@ include $(DEPTH)/config/autoconf.mk include $(topsrcdir)/config/config.mk DIRS = \ highlighter \ webconsole \ sourceeditor \ styleinspector \ + scratchpad \ shared \ $(NULL) -ifdef ENABLE_TESTS -DIRS += scratchpad/test -endif - include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/highlighter/TreePanel.jsm +++ b/browser/devtools/highlighter/TreePanel.jsm @@ -222,20 +222,29 @@ TreePanel.prototype = { { let treeBox = null; let toolbar = this.IUI.toolbar.nextSibling; // Addons bar, typically let toolbarParent = this.IUI.browser.ownerDocument.getElementById("browser-bottombox"); treeBox = this.document.createElement("vbox"); treeBox.id = "inspector-tree-box"; treeBox.state = "open"; // for the registerTools API. - treeBox.minHeight = 10; + try { + treeBox.height = + Services.prefs.getIntPref("devtools.inspector.htmlHeight"); + } catch(e) { + treeBox.height = 112; + } + + treeBox.minHeight = 64; treeBox.flex = 1; toolbarParent.insertBefore(treeBox, toolbar); - this.createResizer(); + + this.IUI.toolbar.setAttribute("treepanel-open", "true"); + treeBox.appendChild(this.treeIFrame); let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel() { this.treeIFrame.removeEventListener("load", boundLoadedInitializeTreePanel, true); this.initializeIFrame(); }.bind(this); @@ -247,38 +256,25 @@ TreePanel.prototype = { if (src != INSPECTOR_URI) { this.treeIFrame.setAttribute("src", INSPECTOR_URI); } else { this.treeIFrame.contentWindow.location.reload(); } }, /** - * Lame resizer on the toolbar. - */ - createResizer: function TP_createResizer() - { - let resizer = this.document.createElement("resizer"); - resizer.id = "inspector-horizontal-splitter"; - resizer.setAttribute("dir", "top"); - resizer.flex = 1; - resizer.setAttribute("element", "inspector-tree-box"); - resizer.height = 24; - this.IUI.toolbar.appendChild(resizer); - this.resizer = resizer; - }, - - /** * Close the TreePanel. */ close: function TP_close() { if (this.openInDock) { - this.IUI.toolbar.removeChild(this.resizer); + this.IUI.toolbar.removeAttribute("treepanel-open"); + let treeBox = this.container; + Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height); let treeBoxParent = treeBox.parentNode; treeBoxParent.removeChild(treeBox); } else { this.container.hidePopup(); } if (this.treePanelDiv) { this.treePanelDiv.ownerPanel = null; @@ -465,16 +461,19 @@ TreePanel.prototype = { // position the editor editor.style.left = editorLeft + "px"; editor.style.top = editorTop + "px"; // set and select the text editorInput.value = aAttrVal; editorInput.select(); + // remove tree key navigation events + this.treeIFrame.removeEventListener("keypress", this.IUI, false); + // listen for editor specific events this.bindEditorEvent(editor, "click", function(aEvent) { aEvent.stopPropagation(); }); this.bindEditorEvent(editor, "dblclick", function(aEvent) { aEvent.stopPropagation(); }); this.bindEditorEvent(editor, "keypress", @@ -522,18 +521,22 @@ TreePanel.prototype = { * Handle keypress events in the editor. * @param aEvent * The keyboard event. */ handleEditorKeypress: function TP_handleEditorKeypress(aEvent) { if (aEvent.which == this.window.KeyEvent.DOM_VK_RETURN) { this.saveEditor(); + aEvent.preventDefault(); + aEvent.stopPropagation(); } else if (aEvent.keyCode == this.window.KeyEvent.DOM_VK_ESCAPE) { this.closeEditor(); + aEvent.preventDefault(); + aEvent.stopPropagation(); } }, /** * Close the editor and cleanup. */ closeEditor: function TP_closeEditor() { @@ -553,16 +556,19 @@ TreePanel.prototype = { this.unbindEditorEvent(editor, "keypress"); // clean up after the editor editorInput.value = ""; editorInput.blur(); this.editingContext = null; this.editingEvents = {}; + // re-add navigation listener + this.treeIFrame.addEventListener("keypress", this.IUI, false); + // event notification Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, null); }, /** * Commit the edits made in the editor, then close it. */ @@ -574,16 +580,17 @@ TreePanel.prototype = { // set the new attribute value on the original target DOM element this.editingContext.repObj.setAttribute(this.editingContext.attrName, editorInput.value); // update the HTML tree attribute value this.editingContext.attrObj.innerHTML = editorInput.value; this.IUI.isDirty = true; + this.IUI.nodeChanged(this.registrationObject); // event notification Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, null); this.closeEditor(); }, @@ -674,18 +681,16 @@ TreePanel.prototype = { destroy: function TP_destroy() { if (this.isOpen()) { this.close(); } domplateUtils.setDOM(null); - delete this.resizer; - if (this.DOMHelpers) { this.DOMHelpers.destroy(); delete this.DOMHelpers; } if (this.treePanelDiv) { this.treePanelDiv.ownerPanel = null; let parent = this.treePanelDiv.parentNode;
--- a/browser/devtools/highlighter/inspector.jsm +++ b/browser/devtools/highlighter/inspector.jsm @@ -44,16 +44,18 @@ const Cu = Components.utils; const Ci = Components.interfaces; const Cr = Components.results; var EXPORTED_SYMBOLS = ["InspectorUI"]; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource:///modules/TreePanel.jsm"); +Cu.import("resource:///modules/devtools/CssRuleView.jsm"); const INSPECTOR_INVISIBLE_ELEMENTS = { "head": true, "base": true, "basefont": true, "isindex": true, "link": true, "meta": true, @@ -72,19 +74,25 @@ const INSPECTOR_NOTIFICATIONS = { // Fires once the Inspector completes the initialization and opens up on // screen. OPENED: "inspector-opened", // Fires once the Inspector is closed. CLOSED: "inspector-closed", + // Fires when the Inspector is reopened after tab-switch. + STATE_RESTORED: "inspector-state-restored", + // Fires when the Tree Panel is opened and initialized. TREEPANELREADY: "inspector-treepanel-ready", + // Fires when the CSS Rule View is opened and initialized. + RULEVIEWREADY: "inspector-ruleview-ready", + // Event notifications for the attribute-value editor EDITOR_OPENED: "inspector-editor-opened", EDITOR_CLOSED: "inspector-editor-closed", EDITOR_SAVED: "inspector-editor-saved", }; /////////////////////////////////////////////////////////////////////////// //// Highlighter @@ -118,30 +126,30 @@ Highlighter.prototype = { this._highlighting = false; this.highlighterContainer = this.chromeDoc.createElement("stack"); this.highlighterContainer.id = "highlighter-container"; this.veilContainer = this.chromeDoc.createElement("vbox"); this.veilContainer.id = "highlighter-veil-container"; + // The controlsBox will host the different interactive + // elements of the highlighter (buttons, toolbars, ...). let controlsBox = this.chromeDoc.createElement("box"); controlsBox.id = "highlighter-controls"; this.highlighterContainer.appendChild(this.veilContainer); this.highlighterContainer.appendChild(controlsBox); stack.appendChild(this.highlighterContainer); // The veil will make the whole page darker except // for the region of the selected box. this.buildVeil(this.veilContainer); - // The controlsBox will host the different interactive - // elements of the highlighter (buttons, toolbars, ...). - this.buildControls(controlsBox); + this.buildInfobar(controlsBox); this.browser.addEventListener("resize", this, true); this.browser.addEventListener("scroll", this, true); this.handleResize(); }, /** @@ -195,30 +203,16 @@ Highlighter.prototype = { this.veilMiddleBox.appendChild(veilRightBox); aParent.appendChild(this.veilTopBox); aParent.appendChild(this.veilMiddleBox); aParent.appendChild(veilBottomBox); }, /** - * Build the controls: - * - * <box id="highlighter-close-button"/> - * - * @param nsIDOMElement aParent - * The container of the controls elements. - */ - buildControls: function Highlighter_buildControls(aParent) - { - this.buildCloseButton(aParent); - this.buildInfobar(aParent); - }, - - /** * Build the node Infobar. * * <box id="highlighter-nodeinfobar-container"> * <box id="Highlighter-nodeinfobar-arrow-top"/> * <vbox id="highlighter-nodeinfobar"> * <label id="highlighter-nodeinfobar-tagname"/> * <label id="highlighter-nodeinfobar-id"/> * <vbox id="highlighter-nodeinfobar-classes"/> @@ -274,48 +268,23 @@ Highlighter.prototype = { idLabel: idLabel, classesBox: classesBox, container: container, barHeight: barHeight, }; }, /** - * Build the close button. - * - * @param nsIDOMElement aParent - * The container of the close-button. - */ - buildCloseButton: function Highlighter_buildCloseButton(aParent) - { - let closeButton = this.chromeDoc.createElement("box"); - closeButton.id = "highlighter-close-button"; - closeButton.appendChild(this.chromeDoc.createElement("image")); - - let boundCloseEventHandler = this.IUI.closeInspectorUI.bind(this.IUI, false); - - closeButton.addEventListener("click", boundCloseEventHandler, false); - - aParent.appendChild(closeButton); - - this.boundCloseEventHandler = boundCloseEventHandler; - this.closeButton = closeButton; - }, - - /** * Destroy the nodes. */ destroy: function Highlighter_destroy() { this.browser.removeEventListener("scroll", this, true); this.browser.removeEventListener("resize", this, true); - this.closeButton.removeEventListener("click", this.boundCloseEventHandler, false); this.boundCloseEventHandler = null; - this.closeButton.parentNode.removeChild(this.closeButton); - this.closeButton = null; this._contentRect = null; this._highlightRect = null; this._highlighting = false; this.veilTopBox = null; this.veilLeftBox = null; this.veilMiddleBox = null; this.veilTransparentBox = null; this.veilContainer = null; @@ -773,16 +742,17 @@ function InspectorUI(aWindow) } InspectorUI.prototype = { browser: null, tools: null, toolEvents: null, inspecting: false, treePanelEnabled: true, + ruleViewEnabled: true, isDirty: false, store: null, /** * Toggle the inspector interface elements on or off. * * @param aEvent * The event that requested the UI change. Toolbar button or menu. @@ -792,16 +762,67 @@ InspectorUI.prototype = { if (this.isInspectorOpen) { this.closeInspectorUI(); } else { this.openInspectorUI(); } }, /** + * Show the Sidebar. + */ + showSidebar: function IUI_showSidebar() + { + this.sidebarBox.removeAttribute("hidden"); + this.sidebarSplitter.removeAttribute("hidden"); + this.stylingButton.checked = true; + + // Activate the first tool in the sidebar, only if none previously- + // selected. We'll want to do a followup to remember selected tool-states. + if (!Array.some(this.sidebarToolbar.children, + function(btn) btn.hasAttribute("checked"))) { + let firstButtonId = this.getToolbarButtonId(this.sidebarTools[0].id); + this.chromeDoc.getElementById(firstButtonId).click(); + } + }, + + /** + * Hide the Sidebar. + */ + hideSidebar: function IUI_hideSidebar() + { + this.sidebarBox.setAttribute("hidden", "true"); + this.sidebarSplitter.setAttribute("hidden", "true"); + this.stylingButton.checked = false; + }, + + /** + * Show or hide the sidebar. Called from the Styling button on the + * highlighter toolbar. + */ + toggleSidebar: function IUI_toggleSidebar() + { + if (!this.isSidebarOpen) { + this.showSidebar(); + } else { + this.hideSidebar(); + } + }, + + /** + * Getter to test if the Sidebar is open or not. + */ + get isSidebarOpen() + { + return this.stylingButton.checked && + !this.sidebarBox.hidden && + !this.sidebarSplitter.hidden; + }, + + /** * Toggle the status of the inspector, starting or stopping it. Invoked * from the toolbar's Inspect button. */ toggleInspection: function IUI_toggleInspection() { if (this.inspecting) { this.stopInspecting(); } else { @@ -869,38 +890,70 @@ InspectorUI.prototype = { this.winID = this.getWindowID(this.win); this.toolbar = this.chromeDoc.getElementById("inspector-toolbar"); this.inspectMenuitem = this.chromeDoc.getElementById("Tools:Inspect"); this.inspectToolbutton = this.chromeDoc.getElementById("inspector-inspect-toolbutton"); this.initTools(); - if (!this.TreePanel && this.treePanelEnabled) { - Cu.import("resource:///modules/TreePanel.jsm", this); - this.treePanel = new this.TreePanel(this.chromeWin, this); + if (this.treePanelEnabled) { + this.treePanel = new TreePanel(this.chromeWin, this); + } + + if (Services.prefs.getBoolPref("devtools.ruleview.enabled") && + !this.toolRegistered("ruleview")) { + this.registerRuleView(); } if (Services.prefs.getBoolPref("devtools.styleinspector.enabled") && !this.toolRegistered("styleinspector")) { this.stylePanel = new StyleInspector(this.chromeWin, this); } this.toolbar.hidden = false; this.inspectMenuitem.setAttribute("checked", true); + // initialize the HTML Breadcrumbs + this.breadcrumbs = new HTMLBreadcrumbs(this); + this.isDirty = false; this.progressListener = new InspectorProgressListener(this); // initialize the highlighter this.initializeHighlighter(); }, /** + * Register the Rule View in the Sidebar. + */ + registerRuleView: function IUI_registerRuleView() + { + let isOpen = this.isRuleViewOpen.bind(this); + + this.ruleViewObject = { + id: "ruleview", + label: this.strings.GetStringFromName("ruleView.label"), + tooltiptext: this.strings.GetStringFromName("ruleView.tooltiptext"), + accesskey: this.strings.GetStringFromName("ruleView.accesskey"), + context: this, + get isOpen() isOpen(), + show: this.openRuleView, + hide: this.closeRuleView, + onSelect: this.selectInRuleView, + panel: null, + unregister: this.destroyRuleView, + sidebar: true, + }; + + this.registerTool(this.ruleViewObject); + }, + + /** * Register and initialize any included tools. */ initTools: function IUI_initTools() { // Extras go here. }, /** @@ -986,35 +1039,42 @@ InspectorUI.prototype = { this.stopInspecting(); this.browser.removeEventListener("keypress", this, true); this.saveToolState(this.winID); this.toolsDo(function IUI_toolsHide(aTool) { this.unregisterTool(aTool); }.bind(this)); + // close the sidebar + this.hideSidebar(); + if (this.highlighter) { this.highlighter.highlighterContainer.removeEventListener("keypress", this, true); this.highlighter.destroy(); this.highlighter = null; } + if (this.breadcrumbs) { + this.breadcrumbs.destroy(); + this.breadcrumbs = null; + } + this.inspectMenuitem.setAttribute("checked", false); this.browser = this.win = null; // null out references to browser and window this.winID = null; this.selection = null; this.closing = false; this.isDirty = false; delete this.treePanel; delete this.stylePanel; delete this.toolbar; - delete this.TreePanel; Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.CLOSED, null); }, /** * Begin inspecting webpage, attach page event listeners, activate * highlighter event listeners. */ startInspecting: function IUI_startInspecting() @@ -1088,19 +1148,34 @@ InspectorUI.prototype = { if (forceUpdate || aNode != this.selection) { this.selection = aNode; if (!this.inspecting) { this.highlighter.highlightNode(this.selection); } } + this.breadcrumbs.update(); + this.toolsSelect(aScroll); }, + /** + * Called when the highlighted node is changed by a tool. + * + * @param object aUpdater + * The tool that triggered the update (if any), that tool's + * onChanged will not be called. + */ + nodeChanged: function IUI_nodeChanged(aUpdater) + { + this.highlighter.highlight(); + this.toolsOnChanged(aUpdater); + }, + ///////////////////////////////////////////////////////////////////////// //// Event Handling highlighterReady: function IUI_highlighterReady() { // Setup the InspectorStore or restore state this.initializeStore(); @@ -1243,16 +1318,118 @@ InspectorUI.prototype = { event.stopPropagation(); break; } break; } }, ///////////////////////////////////////////////////////////////////////// + //// CssRuleView methods + + /** + * Is the cssRuleView open? + */ + isRuleViewOpen: function IUI_isRuleViewOpen() + { + return this.isSidebarOpen && this.ruleButton.hasAttribute("checked") && + (this.sidebarDeck.selectedPanel == this.getToolIframe(this.ruleViewObject)); + }, + + /** + * Convenience getter to retrieve the Rule Button. + */ + get ruleButton() + { + return this.chromeDoc.getElementById( + this.getToolbarButtonId(this.ruleViewObject.id)); + }, + + /** + * Open the CssRuleView. + */ + openRuleView: function IUI_openRuleView() + { + let iframe = this.getToolIframe(this.ruleViewObject); + if (iframe.getAttribute("src")) { + // We're already loading this tool, let it finish. + return; + } + + let boundLoadListener = function() { + iframe.removeEventListener("load", boundLoadListener, true); + let doc = iframe.contentDocument; + + let winID = this.winID; + let ruleViewStore = this.store.getValue(winID, "ruleView"); + if (!ruleViewStore) { + ruleViewStore = {}; + this.store.setValue(winID, "ruleView", ruleViewStore); + } + + this.ruleView = new CssRuleView(doc, ruleViewStore); + + this.boundRuleViewChanged = this.ruleViewChanged.bind(this); + this.ruleView.element.addEventListener("CssRuleViewChanged", + this.boundRuleViewChanged); + + doc.documentElement.appendChild(this.ruleView.element); + this.ruleView.highlight(this.selection); + Services.obs.notifyObservers(null, + INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, null); + }.bind(this); + + iframe.addEventListener("load", boundLoadListener, true); + + iframe.setAttribute("src", "chrome://browser/content/devtools/cssruleview.xul"); + }, + + /** + * Stub to Close the CSS Rule View. Does nothing currently because the + * Rule View lives in the sidebar. + */ + closeRuleView: function IUI_closeRuleView() + { + // do nothing for now + }, + + /** + * Update the selected node in the Css Rule View. + * @param {nsIDOMnode} the selected node. + */ + selectInRuleView: function IUI_selectInRuleView(aNode) + { + if (this.ruleView) + this.ruleView.highlight(aNode); + }, + + ruleViewChanged: function IUI_ruleViewChanged() + { + this.isDirty = true; + this.nodeChanged(this.ruleViewObject); + }, + + /** + * Destroy the rule view. + */ + destroyRuleView: function IUI_destroyRuleView() + { + let iframe = this.getToolIframe(this.ruleViewObject); + iframe.parentNode.removeChild(iframe); + + if (this.ruleView) { + this.ruleView.element.removeEventListener("CssRuleViewChanged", + this.boundRuleViewChanged); + delete boundRuleViewChanged; + this.ruleView.clear(); + delete this.ruleView; + } + }, + + ///////////////////////////////////////////////////////////////////////// //// Utility Methods /** * inspect the given node, highlighting it on the page and selecting the * correct row in the tree panel * * @param aNode * the element in the document to inspect @@ -1392,135 +1569,257 @@ InspectorUI.prototype = { * @returns String */ getToolbarButtonId: function IUI_createButtonId(anId) { return "inspector-" + anId + "-toolbutton"; }, /** + * Save a registered tool's callback for a specified event. + * @param aWidget xul:widget + * @param aEvent a DOM event name + * @param aCallback Function the click event handler for the button + */ + bindToolEvent: function IUI_bindToolEvent(aWidget, aEvent, aCallback) + { + this.toolEvents[aWidget.id + "_" + aEvent] = aCallback; + aWidget.addEventListener(aEvent, aCallback, false); + }, + + /** * Register an external tool with the inspector. * * aRegObj = { * id: "toolname", * context: myTool, - * label: "Button label", + * label: "Button or tab label", * icon: "chrome://somepath.png", * tooltiptext: "Button tooltip", * accesskey: "S", * isOpen: object.property, (getter) returning true if tool is open. * onSelect: object.method, * show: object.method, called to show the tool when button is pressed. * hide: object.method, called to hide the tool when button is pressed. * dim: object.method, called to disable a tool during highlighting. * unregister: object.method, called when tool should be destroyed. - * panel: myTool.panel + * panel: myTool.panel, set if tool is in a separate panel, null otherwise. + * sidebar: boolean, true if tool lives in sidebar tab. * } * * @param aRegObj Object * The Registration Object used to register this tool described * above. The tool should cache this object for later deregistration. */ registerTool: function IUI_registerTool(aRegObj) { if (this.toolRegistered(aRegObj.id)) { return; } this.tools[aRegObj.id] = aRegObj; let buttonContainer = this.chromeDoc.getElementById("inspector-tools"); - let btn = this.chromeDoc.createElement("toolbarbutton"); + let btn; + + // if this is a sidebar tool, create the sidebar features for it and bail. + if (aRegObj.sidebar) { + this.createSidebarTool(aRegObj); + return; + } + + btn = this.chromeDoc.createElement("toolbarbutton"); let buttonId = this.getToolbarButtonId(aRegObj.id); btn.setAttribute("id", buttonId); btn.setAttribute("label", aRegObj.label); btn.setAttribute("tooltiptext", aRegObj.tooltiptext); btn.setAttribute("accesskey", aRegObj.accesskey); btn.setAttribute("image", aRegObj.icon || ""); - buttonContainer.appendChild(btn); + buttonContainer.insertBefore(btn, this.stylingButton); - /** - * Save a registered tool's callback for a specified event. - * @param aWidget xul:widget - * @param aEvent a DOM event name - * @param aCallback Function the click event handler for the button - */ - let toolEvents = this.toolEvents; - function bindToolEvent(aWidget, aEvent, aCallback) { - toolEvents[aWidget.id + "_" + aEvent] = aCallback; - aWidget.addEventListener(aEvent, aCallback, false); - } - - bindToolEvent(btn, "click", + this.bindToolEvent(btn, "click", function IUI_toolButtonClick(aEvent) { if (btn.checked) { this.toolHide(aRegObj); } else { this.toolShow(aRegObj); } }.bind(this)); + // if the tool has a panel, register the popuphiding event if (aRegObj.panel) { - bindToolEvent(aRegObj.panel, "popuphiding", + this.bindToolEvent(aRegObj.panel, "popuphiding", function IUI_toolPanelHiding() { btn.checked = false; }); } }, + get sidebarBox() + { + return this.chromeDoc.getElementById("devtools-sidebar-box"); + }, + + get sidebarToolbar() + { + return this.chromeDoc.getElementById("devtools-sidebar-toolbar"); + }, + + get sidebarDeck() + { + return this.chromeDoc.getElementById("devtools-sidebar-deck"); + }, + + get sidebarSplitter() + { + return this.chromeDoc.getElementById("devtools-side-splitter"); + }, + + get stylingButton() + { + return this.chromeDoc.getElementById("inspector-style-button"); + }, + + /** + * Creates a tab and tabpanel for our tool to reside in. + * @param {Object} aRegObj the Registration Object for our tool. + */ + createSidebarTool: function IUI_createSidebarTab(aRegObj) + { + // toolbutton elements + let btn = this.chromeDoc.createElement("toolbarbutton"); + let buttonId = this.getToolbarButtonId(aRegObj.id); + + btn.id = buttonId; + btn.setAttribute("label", aRegObj.label); + btn.setAttribute("tooltiptext", aRegObj.tooltiptext); + btn.setAttribute("accesskey", aRegObj.accesskey); + btn.setAttribute("image", aRegObj.icon || ""); + btn.setAttribute("type", "radio"); + btn.setAttribute("group", "sidebar-tools"); + this.sidebarToolbar.appendChild(btn); + + // create tool iframe + let iframe = this.chromeDoc.createElement("iframe"); + iframe.id = "devtools-sidebar-iframe-" + aRegObj.id; + iframe.setAttribute("flex", "1"); + this.sidebarDeck.appendChild(iframe); + + // wire up button to show the iframe + this.bindToolEvent(btn, "click", function showIframe() { + this.toolShow(aRegObj); + }.bind(this)); + }, + + /** + * Return the registered object's iframe. + * @param aRegObj see registerTool function. + * @return iframe or null + */ + getToolIframe: function IUI_getToolIFrame(aRegObj) + { + return this.chromeDoc.getElementById("devtools-sidebar-iframe-" + aRegObj.id); + }, + /** * Show the specified tool. * @param aTool Object (see comment for IUI_registerTool) */ toolShow: function IUI_toolShow(aTool) { + let btn = this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id)); + btn.setAttribute("checked", "true"); + if (aTool.sidebar) { + this.sidebarDeck.selectedPanel = this.getToolIframe(aTool); + this.sidebarTools.forEach(function(other) { + if (other != aTool) + this.chromeDoc.getElementById( + this.getToolbarButtonId(other.id)).removeAttribute("checked"); + }.bind(this)); + } + aTool.show.call(aTool.context, this.selection); - this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id)).checked = true; }, /** * Hide the specified tool. * @param aTool Object (see comment for IUI_registerTool) */ toolHide: function IUI_toolHide(aTool) { aTool.hide.call(aTool.context); - this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id)).checked = false; + + let btn = this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id)); + btn.removeAttribute("checked"); + }, + + /** + * Unregister the events associated with the registered tool's widget. + * @param aWidget XUL:widget (toolbarbutton|panel). + * @param aEvent a DOM event. + */ + unbindToolEvent: function IUI_unbindToolEvent(aWidget, aEvent) + { + let toolEvent = aWidget.id + "_" + aEvent; + aWidget.removeEventListener(aEvent, this.toolEvents[toolEvent], false); + delete this.toolEvents[toolEvent] }, /** * Unregister the registered tool, unbinding click events for the buttons * and showing and hiding events for the panel. * @param aRegObj Object * The registration object used to register the tool. */ unregisterTool: function IUI_unregisterTool(aRegObj) { + // if this is a sidebar tool, use the sidebar unregistration method + if (aRegObj.sidebar) { + this.unregisterSidebarTool(aRegObj); + return; + } + let button = this.chromeDoc.getElementById(this.getToolbarButtonId(aRegObj.id)); + let buttonContainer = this.chromeDoc.getElementById("inspector-tools"); - /** - * Unregister the events associated with the registered tool's widget. - * @param aWidget XUL:widget (toolbarbutton|panel). - * @param aEvent a DOM event. - */ - let toolEvents = this.toolEvents; - function unbindToolEvent(aWidget, aEvent) { - let toolEvent = aWidget.id + "_" + aEvent; - aWidget.removeEventListener(aEvent, toolEvents[toolEvent], false); - delete toolEvents[toolEvent] - }; + // unbind click events on button + this.unbindToolEvent(button, "click"); - let buttonContainer = this.chromeDoc.getElementById("inspector-tools"); - unbindToolEvent(button, "click"); + // unbind panel popuphiding events if present. + if (aRegObj.panel) + this.unbindToolEvent(aRegObj.panel, "popuphiding"); - if (aRegObj.panel) - unbindToolEvent(aRegObj.panel, "popuphiding"); - + // remove the button from its container buttonContainer.removeChild(button); + // call unregister callback and remove from collection + if (aRegObj.unregister) + aRegObj.unregister.call(aRegObj.context); + + delete this.tools[aRegObj.id]; + }, + + /** + * Unregister the registered sidebar tool, unbinding click events for the + * button. + * @param aRegObj Object + * The registration object used to register the tool. + */ + unregisterSidebarTool: function IUI_unregisterSidebarTool(aRegObj) + { + // unbind tool button click event + let buttonId = this.getToolbarButtonId(aRegObj.id); + let btn = this.chromeDoc.getElementById(buttonId); + this.unbindToolEvent(btn, "click"); + + // remove sidebar buttons and tools + this.sidebarToolbar.removeChild(btn); + + // call unregister callback and remove from collection, this also removes + // the iframe. if (aRegObj.unregister) aRegObj.unregister.call(aRegObj.context); delete this.tools[aRegObj.id]; }, /** * Save a list of open tools to the inspector store. @@ -1542,23 +1841,34 @@ InspectorUI.prototype = { * Restore tools previously save using saveToolState(). * * @param aWinID The ID of the window to which the associated tools are to be * restored. */ restoreToolState: function IUI_restoreToolState(aWinID) { let openTools = this.store.getValue(aWinID, "openTools"); + let activeSidebarTool; if (openTools) { this.toolsDo(function IUI_toolsOnShow(aTool) { if (aTool.id in openTools) { + if (aTool.sidebar && !this.isSidebarOpen) { + this.showSidebar(); + activeSidebarTool = aTool; + } this.toolShow(aTool); } }.bind(this)); + this.sidebarTools.forEach(function(tool) { + if (tool != activeSidebarTool) + this.chromeDoc.getElementById( + this.getToolbarButtonId(tool.id)).removeAttribute("checked"); + }.bind(this)); } + Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null); }, /** * For each tool in the tools collection select the current node that is * selected in the highlighter * @param aScroll boolean * Do you want to scroll the treepanel? */ @@ -1573,35 +1883,63 @@ InspectorUI.prototype = { }, /** * Dim or undim each tool in the tools collection * @param aState true = dim, false = undim */ toolsDim: function IUI_toolsDim(aState) { - this.toolsDo(function IUI_toolsOnSelect(aTool) { + this.toolsDo(function IUI_toolsDim(aTool) { if (aTool.isOpen && "dim" in aTool) { aTool.dim.call(aTool.context, aState); } }); }, /** + * Notify registered tools of changes to the highlighted element. + * + * @param object aUpdater + * The tool that triggered the update (if any), that tool's + * onChanged will not be called. + */ + toolsOnChanged: function IUI_toolsChanged(aUpdater) + { + this.toolsDo(function IUI_toolsOnChanged(aTool) { + if (aTool.isOpen && ("onChanged" in aTool) && aTool != aUpdater) { + aTool.onChanged.call(aTool.context); + } + }); + }, + + /** * Loop through all registered tools and pass each into the provided function * @param aFunction The function to which each tool is to be passed */ toolsDo: function IUI_toolsDo(aFunction) { for each (let tool in this.tools) { aFunction(tool); } }, /** + * Convenience getter to retrieve only the sidebar tools. + */ + get sidebarTools() + { + let sidebarTools = []; + for each (let tool in this.tools) + if (tool.sidebar) + sidebarTools.push(tool); + return sidebarTools; + }, + + /** * Check if a tool is registered? * @param aId The id of the tool to check */ toolRegistered: function IUI_toolRegistered(aId) { return aId in this.tools; }, @@ -1775,18 +2113,23 @@ InspectorProgressListener.prototype = { function IPL_onStateChange(aProgress, aRequest, aFlag, aStatus) { // Remove myself if the Inspector is no longer open. if (!this.IUI.isInspectorOpen) { this.destroy(); return; } - // Skip non-start states. - if (!(aFlag & Ci.nsIWebProgressListener.STATE_START)) { + let isStart = aFlag & Ci.nsIWebProgressListener.STATE_START; + let isDocument = aFlag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT; + let isNetwork = aFlag & Ci.nsIWebProgressListener.STATE_IS_NETWORK; + let isRequest = aFlag & Ci.nsIWebProgressListener.STATE_IS_REQUEST; + + // Skip non-interesting states. + if (!isStart || !isDocument || !isRequest || !isNetwork) { return; } // If the request is about to happen in a new window, we are not concerned // about the request. if (aProgress.DOMWindow != this.IUI.win) { return; } @@ -1883,23 +2226,473 @@ InspectorProgressListener.prototype = { if (notification) { notificationBox.removeNotification(notification, true); } delete this.IUI; }, }; +/////////////////////////////////////////////////////////////////////////// +//// HTML Breadcrumbs + +/** + * Display the ancestors of the current node and its children. + * Only one "branch" of children are displayed (only one line). + * + * Mechanism: + * . If no nodes displayed yet: + * then display the ancestor of the selected node and the selected node; + * else select the node; + * . If the selected node is the last node displayed, append its first (if any). + * + * @param object aInspector + * The InspectorUI instance. + */ +function HTMLBreadcrumbs(aInspector) +{ + this.IUI = aInspector; + this.DOMHelpers = new DOMHelpers(this.IUI.win); + this._init(); +} + +HTMLBreadcrumbs.prototype = { + _init: function BC__init() + { + this.container = this.IUI.chromeDoc.getElementById("inspector-breadcrumbs"); + this.container.addEventListener("mousedown", this, true); + + // We will save a list of already displayed nodes in this array. + this.nodeHierarchy = []; + + // Last selected node in nodeHierarchy. + this.currentIndex = -1; + + // Siblings menu + this.menu = this.IUI.chromeDoc.createElement("menupopup"); + this.menu.id = "inspector-breadcrumbs-menu"; + + let popupSet = this.IUI.chromeDoc.getElementById("mainPopupSet"); + popupSet.appendChild(this.menu); + + this.menu.addEventListener("popuphiding", (function() { + while (this.menu.hasChildNodes()) { + this.menu.removeChild(this.menu.firstChild); + } + let button = this.container.querySelector("button[siblings-menu-open]"); + button.removeAttribute("siblings-menu-open"); + }).bind(this), false); + }, + + /** + * Build a string that represents the node: tagName#id.class1.class2. + * + * @param aNode The node to pretty-print + * @returns a string + */ + prettyPrintNodeAsText: function BC_prettyPrintNodeText(aNode) + { + let text = aNode.tagName.toLowerCase(); + if (aNode.id) { + text += "#" + aNode.id; + } + for (let i = 0; i < aNode.classList.length; i++) { + text += "." + aNode.classList[i]; + } + return text; + }, + + + /** + * Build <label>s that represent the node: + * <label class="inspector-breadcrumbs-tag">tagName</label> + * <label class="inspector-breadcrumbs-id">#id</label> + * <label class="inspector-breadcrumbs-classes">.class1.class2</label> + * + * @param aNode The node to pretty-print + * @returns a document fragment. + */ + prettyPrintNodeAsXUL: function BC_prettyPrintNodeXUL(aNode) + { + let fragment = this.IUI.chromeDoc.createDocumentFragment(); + + let tagLabel = this.IUI.chromeDoc.createElement("label"); + tagLabel.className = "inspector-breadcrumbs-tag plain"; + + let idLabel = this.IUI.chromeDoc.createElement("label"); + idLabel.className = "inspector-breadcrumbs-id plain"; + + let classesLabel = this.IUI.chromeDoc.createElement("label"); + classesLabel.className = "inspector-breadcrumbs-classes plain"; + + tagLabel.textContent = aNode.tagName.toLowerCase(); + idLabel.textContent = aNode.id ? ("#" + aNode.id) : ""; + + let classesText = ""; + for (let i = 0; i < aNode.classList.length; i++) { + classesText += "." + aNode.classList[i]; + } + classesLabel.textContent = classesText; + + fragment.appendChild(tagLabel); + fragment.appendChild(idLabel); + fragment.appendChild(classesLabel); + + return fragment; + }, + + /** + * Open the sibling menu. + * + * @param aButton the button representing the node. + * @param aNode the node we want the siblings from. + */ + openSiblingMenu: function BC_openSiblingMenu(aButton, aNode) + { + let title = this.IUI.chromeDoc.createElement("menuitem"); + title.setAttribute("label", + this.IUI.strings.GetStringFromName("breadcrumbs.siblings")); + title.setAttribute("disabled", "true"); + + let separator = this.IUI.chromeDoc.createElement("menuseparator"); + + this.menu.appendChild(title); + this.menu.appendChild(separator); + + let fragment = this.IUI.chromeDoc.createDocumentFragment(); + + let nodes = aNode.parentNode.childNodes; + for (let i = 0; i < nodes.length; i++) { + if (nodes[i].nodeType == aNode.ELEMENT_NODE) { + let item = this.IUI.chromeDoc.createElement("menuitem"); + let inspector = this.IUI; + if (nodes[i] === aNode) { + item.setAttribute("disabled", "true"); + item.setAttribute("checked", "true"); + } + + item.setAttribute("type", "radio"); + item.setAttribute("label", this.prettyPrintNodeAsText(nodes[i])); + + item.onmouseup = (function(aNode) { + return function() { + inspector.select(aNode, true, true); + } + })(nodes[i]); + + fragment.appendChild(item); + } + } + this.menu.appendChild(fragment); + this.menu.openPopup(aButton, "before_start", 0, 0, true, false); + }, + + /** + * Generic event handler. + * + * @param nsIDOMEvent aEvent + * The DOM event object. + */ + handleEvent: function BC_handleEvent(aEvent) + { + if (aEvent.type == "mousedown") { + // on Click and Hold, open the Siblings menu + + let timer; + let container = this.container; + let window = this.IUI.win; + + function openMenu(aEvent) { + cancelHold(); + let target = aEvent.originalTarget; + if (target.tagName == "button") { + target.onBreadcrumbsHold(); + target.setAttribute("siblings-menu-open", "true"); + } + } + + function handleClick(aEvent) { + cancelHold(); + let target = aEvent.originalTarget; + if (target.tagName == "button") { + target.onBreadcrumbsClick(); + } + } + + function cancelHold(aEvent) { + window.clearTimeout(timer); + container.removeEventListener("mouseout", cancelHold, false); + container.removeEventListener("mouseup", handleClick, false); + } + + container.addEventListener("mouseout", cancelHold, false); + container.addEventListener("mouseup", handleClick, false); + timer = window.setTimeout(openMenu, 500, aEvent); + } + }, + + /** + * Remove nodes and delete properties. + */ + destroy: function BC_destroy() + { + this.empty(); + this.container.removeEventListener("mousedown", this, true); + this.menu.parentNode.removeChild(this.menu); + this.container = null; + this.nodeHierarchy = null; + }, + + /** + * Empty the breadcrumbs container. + */ + empty: function BC_empty() + { + while (this.container.hasChildNodes()) { + this.container.removeChild(this.container.firstChild); + } + }, + + /** + * Re-init the cache and remove all the buttons. + */ + invalidateHierarchy: function BC_invalidateHierarchy() + { + this.menu.hidePopup(); + this.nodeHierarchy = []; + this.empty(); + }, + + /** + * Set which button represent the selected node. + * + * @param aIdx Index of the displayed-button to select + */ + setCursor: function BC_setCursor(aIdx) + { + // Unselect the previously selected button + if (this.currentIndex > -1 && this.currentIndex < this.nodeHierarchy.length) { + this.nodeHierarchy[this.currentIndex].button.removeAttribute("checked"); + } + if (aIdx > -1) { + this.nodeHierarchy[aIdx].button.setAttribute("checked", "true"); + } + this.currentIndex = aIdx; + }, + + /** + * Get the index of the node in the cache. + * + * @param aNode + * @returns integer the index, -1 if not found + */ + indexOf: function BC_indexOf(aNode) + { + let i = this.nodeHierarchy.length - 1; + for (let i = this.nodeHierarchy.length - 1; i >= 0; i--) { + if (this.nodeHierarchy[i].node === aNode) { + return i; + } + } + return -1; + }, + + /** + * Remove all the buttons and their references in the cache + * after a given index. + * + * @param aIdx + */ + cutAfter: function BC_cutAfter(aIdx) + { + while (this.nodeHierarchy.length > (aIdx + 1)) { + let toRemove = this.nodeHierarchy.pop(); + this.container.removeChild(toRemove.button); + } + }, + + /** + * Build a button representing the node. + * + * @param aNode The node from the page. + * @returns aNode The <button>. + */ + buildButton: function BC_buildButton(aNode) + { + let button = this.IUI.chromeDoc.createElement("button"); + let inspector = this.IUI; + button.appendChild(this.prettyPrintNodeAsXUL(aNode)); + button.className = "inspector-breadcrumbs-button"; + + button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(aNode)); + + button.onBreadcrumbsClick = function onBreadcrumbsClick() { + inspector.stopInspecting(); + inspector.select(aNode, true, true); + }; + + button.onclick = (function _onBreadcrumbsRightClick(aEvent) { + if (aEvent.button == 2) { + this.openSiblingMenu(button, aNode); + } + }).bind(this); + + button.onBreadcrumbsHold = (function _onBreadcrumbsHold() { + this.openSiblingMenu(button, aNode); + }).bind(this); + return button; + }, + + /** + * Connecting the end of the breadcrumbs to a node. + * + * @param aNode The node to reach. + */ + expand: function BC_expand(aNode) + { + let fragment = this.IUI.chromeDoc.createDocumentFragment(); + let toAppend = aNode; + let lastButtonInserted = null; + let originalLength = this.nodeHierarchy.length; + let stopNode = null; + if (originalLength > 0) { + stopNode = this.nodeHierarchy[originalLength - 1].node; + } + while (toAppend && toAppend.tagName && toAppend != stopNode) { + let button = this.buildButton(toAppend); + fragment.insertBefore(button, lastButtonInserted); + lastButtonInserted = button; + this.nodeHierarchy.splice(originalLength, 0, {node: toAppend, button: button}); + toAppend = this.DOMHelpers.getParentObject(toAppend); + } + this.container.appendChild(fragment, this.container.firstChild); + }, + + /** + * Get a child of a node that can be displayed in the breadcrumbs. + * By default, we want a node that can highlighted by the highlighter. + * If no highlightable child is found, we return the first node of type + * ELEMENT_NODE. + * + * @param aNode The parent node. + * @returns nsIDOMNode|null + */ + getFirstHighlightableChild: function BC_getFirstHighlightableChild(aNode) + { + let nextChild = this.DOMHelpers.getChildObject(aNode, 0); + let fallback = null; + + while (nextChild) { + if (this.IUI.highlighter.isNodeHighlightable(nextChild)) { + return nextChild; + } + if (!fallback && nextChild.nodeType == aNode.ELEMENT_NODE) { + fallback = nextChild; + } + nextChild = this.DOMHelpers.getNextSibling(nextChild); + } + return fallback; + }, + + /** + * Find the "youngest" ancestor of a node which is already in the breadcrumbs. + * + * @param aNode + * @returns Index of the ancestor in the cache + */ + getCommonAncestor: function BC_getCommonAncestor(aNode) + { + let node = aNode; + while (node) { + let idx = this.indexOf(node); + if (idx > -1) { + return idx; + } else { + node = this.DOMHelpers.getParentObject(node); + } + } + return -1; + }, + + /** + * Make sure that the latest node in the breadcrumbs is not the selected node + * if the selected node still has children. + */ + ensureFirstChild: function BC_ensureFirstChild() + { + // If the last displayed node is the selected node + if (this.currentIndex == this.nodeHierarchy.length - 1) { + let node = this.nodeHierarchy[this.currentIndex].node; + let child = this.getFirstHighlightableChild(node); + // If the node has a child + if (child) { + // Show this child + this.expand(child); + } + } + }, + + /** + * Ensure the selected node is visible. + */ + scroll: function BC_scroll() + { + // FIXME bug 684352: make sure its immediate neighbors are visible too. + + let scrollbox = this.container; + let element = this.nodeHierarchy[this.currentIndex].button; + scrollbox.ensureElementIsVisible(element); + }, + + /** + * Update the breadcrumbs display when a new node is selected. + */ + update: function BC_update() + { + this.menu.hidePopup(); + + let selection = this.IUI.selection; + let idx = this.indexOf(selection); + + // Is the node already displayed in the breadcrumbs? + if (idx > -1) { + // Yes. We select it. + this.setCursor(idx); + } else { + // No. Is the breadcrumbs display empty? + if (this.nodeHierarchy.length > 0) { + // No. We drop all the element that are not direct ancestors + // of the selection + let parent = this.DOMHelpers.getParentObject(selection); + let idx = this.getCommonAncestor(parent); + this.cutAfter(idx); + } + // we append the missing button between the end of the breadcrumbs display + // and the current node. + this.expand(selection); + + // we select the current node button + idx = this.indexOf(selection); + this.setCursor(idx); + } + // Add the first child of the very last node of the breadcrumbs if possible. + this.ensureFirstChild(); + + // Make sure the selected node and its neighbours are visible. + this.scroll(); + } +} + ///////////////////////////////////////////////////////////////////////// //// Initializers XPCOMUtils.defineLazyGetter(InspectorUI.prototype, "strings", function () { - return Services.strings. - createBundle("chrome://browser/locale/inspector.properties"); + return Services.strings.createBundle( + "chrome://browser/locale/devtools/inspector.properties"); }); XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () { var obj = {}; Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj); return obj.StyleInspector; });
--- a/browser/devtools/highlighter/test/Makefile.in +++ b/browser/devtools/highlighter/test/Makefile.in @@ -58,15 +58,21 @@ include $(topsrcdir)/config/rules.mk browser_inspector_bug_665880.js \ browser_inspector_bug_674871.js \ browser_inspector_editor.js \ browser_inspector_bug_566084_location_changed.js \ browser_inspector_infobar.js \ browser_inspector_bug_690361.js \ browser_inspector_bug_672902_keyboard_shortcuts.js \ browser_inspector_keybindings.js \ + browser_inspector_breadcrumbs.html \ + browser_inspector_breadcrumbs.js \ + browser_inspector_bug_699308_iframe_navigation.js \ + browser_inspector_changes.js \ + browser_inspector_ruleviewstore.js \ + browser_inspector_duplicate_ruleview.js \ $(NULL) # Disabled due to constant failures # browser_inspector_treePanel_click.js \ libs:: $(_BROWSER_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644 --- /dev/null +++ b/browser/devtools/highlighter/test/browser_inspector_breadcrumbs.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> + <head> + <style> + div { + min-height: 10px; min-width: 10px; + border: 1px solid red; + margin: 10px; + } + </style> + </head> + <body> + <article id="i1"> + <div id="i11"> + <div id="i111"> + <div id="i1111"> + </div> + </div> + </div> + </article> + <article id="i2"> + <div id="i21"> + <div id="i211"> + <div id="i2111"> + </div> + </div> + </div> + <div id="i22"> + <div id="i221"> + </div> + <div id="i222"> + <div id="i2221"> + <div id="i22211"> + </div> + </div> + </div> + </div> + </article> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/browser/devtools/highlighter/test/browser_inspector_breadcrumbs.js @@ -0,0 +1,105 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() +{ + waitForExplicitFinish(); + + let nodes = [ + {nodeId: "i1111", result: "i1 i11 i111 i1111"}, + {nodeId: "i22", result: "i2 i22 i221"}, + {nodeId: "i2111", result: "i2 i21 i211 i2111"}, + {nodeId: "i21", result: "i2 i21 i211 i2111"}, + {nodeId: "i22211", result: "i2 i22 i222 i2221 i22211"}, + {nodeId: "i22", result: "i2 i22 i222 i2221 i22211"}, + ]; + + let doc; + let nodes; + let cursor; + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onload() { + gBrowser.selectedBrowser.removeEventListener("load", onload, true); + doc = content.document; + waitForFocus(setupTest, content); + }, true); + + content.location = "http://mochi.test:8888/browser/browser/devtools/highlighter/test/browser_inspector_breadcrumbs.html"; + + function setupTest() + { + for (let i = 0; i < nodes.length; i++) { + let node = doc.getE