diff --git a/Makefile.in b/Makefile.in index a223f35b47ca..a0e9d39a5237 100644 --- a/Makefile.in +++ b/Makefile.in @@ -134,6 +134,10 @@ endif # WINNT ifeq ($(OS_ARCH),WINNT) # we want to copy PDB files on Windows MAKE_SYM_STORE_ARGS := -c +ifeq (,$(CYGWIN_WRAPPER)) +# this doesn't work with Cygwin Python +MAKE_SYM_STORE_ARGS += --vcs-info +endif DUMP_SYMS_BIN ?= $(topsrcdir)/toolkit/crashreporter/tools/win32/dump_syms.exe # PDB files don't get moved to dist, so we need to scan the whole objdir MAKE_SYM_STORE_PATH := . diff --git a/accessible/public/nsIAccessibleRetrieval.idl b/accessible/public/nsIAccessibleRetrieval.idl index eb49b2153349..5fafb13b41f5 100644 --- a/accessible/public/nsIAccessibleRetrieval.idl +++ b/accessible/public/nsIAccessibleRetrieval.idl @@ -56,7 +56,7 @@ interface nsIDOMDOMStringList; * * @status UNDER_REVIEW */ -[scriptable, uuid(2d8c1b1b-7a3f-4962-8a88-81ca019c11e9)] +[scriptable, uuid(56c34b1a-d390-44f4-89c3-6935c0e4e3fa)] interface nsIAccessibleRetrieval : nsISupports { /** @@ -141,21 +141,30 @@ interface nsIAccessibleRetrieval : nsISupports * an accessible does not already exist for this DOM node. */ nsIAccessible getCachedAccessible(in nsIDOMNode aNode, in nsIWeakReference aShell); - + /** * Returns accessible role as a string. * * @param aRole - the accessible role constants. */ AString getStringRole(in unsigned long aRole); - + /** * Returns list which contains accessible states as a strings. * * @param aStates - accessible states. * @param aExtraStates - accessible extra states. */ - nsIDOMDOMStringList getStringStates(in unsigned long aStates, in unsigned long aExtraStates); + nsIDOMDOMStringList getStringStates(in unsigned long aStates, + in unsigned long aExtraStates); + + /** + * Get the type of accessible event as a string. + * + * @param aEventType - the accessible event type constant + * @return - accessible event type presented as human readable string + */ + AString getStringEventType(in unsigned long aEventType); }; diff --git a/accessible/src/atk/Makefile.in b/accessible/src/atk/Makefile.in index 6c72a7694863..771172b7cd3b 100644 --- a/accessible/src/atk/Makefile.in +++ b/accessible/src/atk/Makefile.in @@ -50,6 +50,7 @@ REQUIRES = content \ dom \ editor \ gfx \ + thebes \ intl \ layout \ locale \ diff --git a/accessible/src/base/Makefile.in b/accessible/src/base/Makefile.in index 1bfa38bd270e..179486fd13b1 100644 --- a/accessible/src/base/Makefile.in +++ b/accessible/src/base/Makefile.in @@ -54,6 +54,7 @@ REQUIRES = appshell \ dom \ editor \ gfx \ + thebes \ intl \ layout \ locale \ diff --git a/accessible/src/base/nsAccessibilityService.cpp b/accessible/src/base/nsAccessibilityService.cpp index 5780fab1b148..cc0050af8a81 100644 --- a/accessible/src/base/nsAccessibilityService.cpp +++ b/accessible/src/base/nsAccessibilityService.cpp @@ -1040,6 +1040,20 @@ nsAccessibilityService::GetStringStates(PRUint32 aStates, PRUint32 aExtraStates, return NS_OK; } +// nsIAccessibleRetrieval::getStringEventType() +NS_IMETHODIMP +nsAccessibilityService::GetStringEventType(PRUint32 aEventType, + nsAString& aString) +{ + if ( aEventType >= NS_ARRAY_LENGTH(kEventTypeNames)) { + aString.AssignLiteral("unknown"); + return NS_OK; + } + + CopyUTF8toUTF16(kEventTypeNames[aEventType], aString); + return NS_OK; +} + /** * GetAccessibleFor - get an nsIAccessible from a DOM node */ diff --git a/accessible/src/base/nsAccessibilityService.h b/accessible/src/base/nsAccessibilityService.h index 58772e6ddb9d..162cac1aabe8 100644 --- a/accessible/src/base/nsAccessibilityService.h +++ b/accessible/src/base/nsAccessibilityService.h @@ -52,6 +52,79 @@ class nsIDocShell; class nsIPresShell; class nsIContent; +class nsAccessibilityService : public nsIAccessibilityService, + public nsIObserver, + public nsIWebProgressListener, + public nsSupportsWeakReference +{ +public: + nsAccessibilityService(); + virtual ~nsAccessibilityService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIACCESSIBLERETRIEVAL + NS_DECL_NSIACCESSIBILITYSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSIWEBPROGRESSLISTENER + + /** + * Return presentation shell for the given node. + * + * @param aNode - the given DOM node. + */ + static nsresult GetShellFromNode(nsIDOMNode *aNode, + nsIWeakReference **weakShell); + + /** + * Return accessibility service (static instance of this class). + */ + static nsresult GetAccessibilityService(nsIAccessibilityService** aResult); + +private: + /** + * Return presentation shell, DOM node for the given frame. + * + * @param aFrame - the given frame + * @param aRealFrame [out] - the given frame casted to nsIFrame + * @param aShell [out] - presentation shell for DOM node associated with the + * given frame + * @param aContent [out] - DOM node associated with the given frame + */ + nsresult GetInfo(nsISupports *aFrame, nsIFrame **aRealFrame, + nsIWeakReference **aShell, + nsIDOMNode **aContent); + + /** + * Initialize an accessible and cache it. The method should be called for + * every created accessible. + * + * @param aAccessibleIn - accessible to initialize. + */ + nsresult InitAccessible(nsIAccessible *aAccessibleIn, nsIAccessible **aAccessibleOut); + + /** + * Return accessible object for elements implementing nsIAccessibleProvider + * interface. + * + * @param aNode - DOM node that accessible is returned for. + */ + nsresult GetAccessibleByType(nsIDOMNode *aNode, nsIAccessible **aAccessible); + + /** + * Return accessible object if parent is a deck frame. + * + * @param aNode - DOMNode that accessible is returned for. + */ + nsresult GetAccessibleForDeckChildren(nsIDOMNode *aNode, + nsIAccessible **aAccessible); + + static nsAccessibilityService *gAccessibilityService; +}; + +/** + * Map nsIAccessibleRole constants to strings. Used by + * nsIAccessibleRetrieval::getStringRole() method. + */ static const char kRoleNames[][20] = { "nothing", //ROLE_NOTHING "titlebar", //ROLE_TITLEBAR @@ -172,42 +245,102 @@ static const char kRoleNames[][20] = { "image map" //ROLE_IMAGE_MAP }; -class nsAccessibilityService : public nsIAccessibilityService, - public nsIObserver, - public nsIWebProgressListener, - public nsSupportsWeakReference -{ -public: - nsAccessibilityService(); - virtual ~nsAccessibilityService(); - - NS_DECL_ISUPPORTS - NS_DECL_NSIACCESSIBLERETRIEVAL - NS_DECL_NSIACCESSIBILITYSERVICE - NS_DECL_NSIOBSERVER - NS_DECL_NSIWEBPROGRESSLISTENER - - static nsresult GetShellFromNode(nsIDOMNode *aNode, nsIWeakReference **weakShell); - static nsresult GetAccessibilityService(nsIAccessibilityService** aResult); - -private: - nsresult GetInfo(nsISupports* aFrame, nsIFrame** aRealFrame, nsIWeakReference** aShell, nsIDOMNode** aContent); - void GetOwnerFor(nsIPresShell *aPresShell, nsIPresShell **aOwnerShell, nsIContent **aOwnerContent); - nsIContent* FindContentForDocShell(nsIPresShell* aPresShell, nsIContent* aContent, nsIDocShell* aDocShell); - static nsAccessibilityService *gAccessibilityService; - nsresult InitAccessible(nsIAccessible *aAccessibleIn, nsIAccessible **aAccessibleOut); - - /** - * Return accessible object for elements implementing nsIAccessibleProvider - * interface. - */ - nsresult GetAccessibleByType(nsIDOMNode *aNode, nsIAccessible **aAccessible); - PRBool HasListener(nsIContent *aContent, nsAString& aEventType); - - /** - * Return accessible object if parent is a deck frame - */ - nsresult GetAccessibleForDeckChildren(nsIDOMNode *aNode, nsIAccessible **aAccessible); +/** + * Map nsIAccessibleEvents constants to strings. Used by + * nsIAccessibleRetrieval::getStringEventType() method. + */ +static const char kEventTypeNames[][40] = { + "unknown", // + "DOM node create", // EVENT_DOM_CREATE + "DOM node destroy", // EVENT_DOM_DESTROY + "DOM node significant change", // EVENT_DOM_SIGNIFICANT_CHANGE + "async show", // EVENT_ASYNCH_SHOW + "async hide", // EVENT_ASYNCH_HIDE + "async significant change", // EVENT_ASYNCH_SIGNIFICANT_CHANGE + "active decendent change", // EVENT_ACTIVE_DECENDENT_CHANGED + "focus", // EVENT_FOCUS + "state change", // EVENT_STATE_CHANGE + "location change", // EVENT_LOCATION_CHANGE + "name changed", // EVENT_NAME_CHANGE + "description change", // EVENT_DESCRIPTION_CHANGE + "value change", // EVENT_VALUE_CHANGE + "help change", // EVENT_HELP_CHANGE + "default action change", // EVENT_DEFACTION_CHANGE + "action change", // EVENT_ACTION_CHANGE + "accelerator change", // EVENT_ACCELERATOR_CHANGE + "selection", // EVENT_SELECTION + "selection add", // EVENT_SELECTION_ADD + "selection remove", // EVENT_SELECTION_REMOVE + "selection within", // EVENT_SELECTION_WITHIN + "alert", // EVENT_ALERT + "foreground", // EVENT_FOREGROUND + "menu start", // EVENT_MENU_START + "menu end", // EVENT_MENU_END + "menupopup start", // EVENT_MENUPOPUP_START + "menupopup end", // EVENT_MENUPOPUP_END + "capture start", // EVENT_CAPTURE_START + "capture end", // EVENT_CAPTURE_END + "movesize start", // EVENT_MOVESIZE_START + "movesize end", // EVENT_MOVESIZE_END + "contexthelp start", // EVENT_CONTEXTHELP_START + "contexthelp end", // EVENT_CONTEXTHELP_END + "dragdrop start", // EVENT_DRAGDROP_START + "dragdrop end", // EVENT_DRAGDROP_END + "dialog start", // EVENT_DIALOG_START + "dialog end", // EVENT_DIALOG_END + "scrolling start", // EVENT_SCROLLING_START + "scrolling end", // EVENT_SCROLLING_END + "minimize start", // EVENT_MINIMIZE_START + "minimize end", // EVENT_MINIMIZE_END + "document load start", // EVENT_DOCUMENT_LOAD_START + "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 + "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 + "text column changed", // EVENT_TEXT_COLUMN_CHANGED + "section changed", // EVENT_SECTION_CHANGED + "table caption changed", // EVENT_TABLE_CAPTION_CHANGED + "table model changed", // EVENT_TABLE_MODEL_CHANGED + "table summary changed", // EVENT_TABLE_SUMMARY_CHANGED + "table row description changed", // EVENT_TABLE_ROW_DESCRIPTION_CHANGED + "table row header changed", // EVENT_TABLE_ROW_HEADER_CHANGED + "table row insert", // EVENT_TABLE_ROW_INSERT + "table row delete", // EVENT_TABLE_ROW_DELETE + "table row reorder", // EVENT_TABLE_ROW_REORDER + "table column description changed", // EVENT_TABLE_COLUMN_DESCRIPTION_CHANGED + "table column header changed", // EVENT_TABLE_COLUMN_HEADER_CHANGED + "table column insert", // EVENT_TABLE_COLUMN_INSERT + "table column delete", // EVENT_TABLE_COLUMN_DELETE + "table column reorder", // EVENT_TABLE_COLUMN_REORDER + "window activate", // EVENT_WINDOW_ACTIVATE + "window create", // EVENT_WINDOW_CREATE + "window deactivate", // EVENT_WINDOW_DEACTIVATE + "window destroy", // EVENT_WINDOW_DESTROY + "window maximize", // EVENT_WINDOW_MAXIMIZE + "window minimize", // EVENT_WINDOW_MINIMIZE + "window resize", // EVENT_WINDOW_RESIZE + "window restore", // EVENT_WINDOW_RESTORE + "hyperlink end index changed", // EVENT_HYPERLINK_END_INDEX_CHANGED + "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 + "internal load" // EVENT_INTERNAL_LOAD }; #endif /* __nsIAccessibilityService_h__ */ diff --git a/accessible/src/mac/Makefile.in b/accessible/src/mac/Makefile.in index e99ae3d3f2c8..29a2d0e6d694 100644 --- a/accessible/src/mac/Makefile.in +++ b/accessible/src/mac/Makefile.in @@ -51,6 +51,7 @@ REQUIRES = content \ dom \ editor \ gfx \ + thebes \ intl \ locale \ string \ diff --git a/accessible/src/msaa/Makefile.in b/accessible/src/msaa/Makefile.in index 678655d09c43..608f68ae2314 100644 --- a/accessible/src/msaa/Makefile.in +++ b/accessible/src/msaa/Makefile.in @@ -50,6 +50,7 @@ REQUIRES = content \ dom \ editor \ gfx \ + thebes \ htmlparser \ intl \ layout \ diff --git a/accessible/src/other/Makefile.in b/accessible/src/other/Makefile.in index d03ab64a19fa..523c710c8669 100755 --- a/accessible/src/other/Makefile.in +++ b/accessible/src/other/Makefile.in @@ -50,6 +50,7 @@ REQUIRES = content \ dom \ editor \ gfx \ + thebes \ htmlparser \ intl \ layout \ diff --git a/accessible/src/xforms/Makefile.in b/accessible/src/xforms/Makefile.in index 1b5f77117957..508c09f6d9ac 100755 --- a/accessible/src/xforms/Makefile.in +++ b/accessible/src/xforms/Makefile.in @@ -52,6 +52,7 @@ REQUIRES = content \ dom \ editor \ gfx \ + thebes \ intl \ layout \ locale \ diff --git a/accessible/src/xul/Makefile.in b/accessible/src/xul/Makefile.in index d666523e134c..7fde713f37f4 100644 --- a/accessible/src/xul/Makefile.in +++ b/accessible/src/xul/Makefile.in @@ -52,6 +52,7 @@ REQUIRES = content \ dom \ editor \ gfx \ + thebes \ intl \ layout \ locale \ diff --git a/accessible/src/xul/nsXULMenuAccessible.cpp b/accessible/src/xul/nsXULMenuAccessible.cpp index cf7ee4b7c1b4..5e0d53356517 100644 --- a/accessible/src/xul/nsXULMenuAccessible.cpp +++ b/accessible/src/xul/nsXULMenuAccessible.cpp @@ -657,23 +657,21 @@ void nsXULMenupopupAccessible::GenerateMenu(nsIDOMNode *aNode) } } -NS_IMETHODIMP nsXULMenupopupAccessible::GetName(nsAString& _retval) +NS_IMETHODIMP +nsXULMenupopupAccessible::GetName(nsAString& aName) { - nsCOMPtr element(do_QueryInterface(mDOMNode)); - NS_ASSERTION(element, "No element for popup node!"); + aName.Truncate(); - while (element) { - element->GetAttribute(NS_LITERAL_STRING("label"), _retval); - if (!_retval.IsEmpty()) - return NS_OK; - nsCOMPtr parentNode, node(do_QueryInterface(element)); - if (!node) - return NS_ERROR_FAILURE; - node->GetParentNode(getter_AddRefs(parentNode)); - element = do_QueryInterface(parentNode); + if (!mDOMNode) + return NS_ERROR_FAILURE; + + nsCOMPtr content(do_QueryInterface(mDOMNode)); + while (content && aName.IsEmpty()) { + content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::label, aName); + content = content->GetParent(); } - return NS_ERROR_FAILURE; + return NS_OK; } NS_IMETHODIMP nsXULMenupopupAccessible::GetRole(PRUint32 *aRole) diff --git a/accessible/src/xul/nsXULTreeAccessible.cpp b/accessible/src/xul/nsXULTreeAccessible.cpp index 6b5a57f2f7a0..6c915a571bb6 100644 --- a/accessible/src/xul/nsXULTreeAccessible.cpp +++ b/accessible/src/xul/nsXULTreeAccessible.cpp @@ -721,20 +721,36 @@ nsXULTreeitemAccessible::GetAttributesInternal(nsIPersistentProperties *aAttribu rv = view->GetLevel(mRow, &level); NS_ENSURE_SUCCESS(rv, rv); - PRInt32 lvl = -1; - PRInt32 startIndex = mRow; - for (;startIndex - 1 > 0 && - NS_SUCCEEDED(view->GetLevel(startIndex - 1, &lvl)) && lvl != level; - startIndex--); + PRInt32 topCount = 1; + for (PRInt32 index = mRow - 1; index >= 0; index--) { + PRInt32 lvl = -1; + if (NS_SUCCEEDED(view->GetLevel(index, &lvl))) { + if (lvl < level) + break; - lvl = -1; - PRInt32 endIndex = mRow; - for (;endIndex - 1 > 0 && - NS_SUCCEEDED(view->GetLevel(endIndex - 1, &lvl)) && lvl != level; - endIndex--); + if (lvl == level) + topCount++; + } + } - PRInt32 setSize = endIndex - startIndex + 1; - PRInt32 posInSet = mRow - startIndex + 1; + PRInt32 rowCount = 0; + rv = view->GetRowCount(&rowCount); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt32 bottomCount = 0; + for (PRInt32 index = mRow + 1; index < rowCount; index++) { + PRInt32 lvl = -1; + if (NS_SUCCEEDED(view->GetLevel(index, &lvl))) { + if (lvl < level) + break; + + if (lvl == level) + bottomCount++; + } + } + + PRInt32 setSize = topCount + bottomCount; + PRInt32 posInSet = topCount; // set the group attributes nsAccUtils::SetAccGroupAttrs(aAttributes, level + 1, posInSet, setSize); diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 0147721aa4b8..41e2530d9d35 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -305,9 +305,10 @@ pref("javascript.options.showInConsole", false); pref("dom.disable_window_open_feature.status", true); // This is the pref to control the location bar, change this to true to // force this instead of or in addition to the status bar - this makes -// the origin of popup windows more obvious to avoid spoofing but we -// cannot do it by default because it affects UE for web applications. -pref("dom.disable_window_open_feature.location", false); +// the origin of popup windows more obvious to avoid spoofing. We would +// rather not do it by default because it affects UE for web applications, but +// without it there isn't a really good way to prevent chrome spoofing, see bug 337344 +pref("dom.disable_window_open_feature.location", true); pref("dom.disable_window_status_change", true); // allow JS to move and resize existing windows pref("dom.disable_window_move_resize", false); diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js index 690326d7089b..feca1eb349c9 100644 --- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -58,10 +58,7 @@ var PlacesCommandHook = { if (aEvent.originalTarget != this.panel) return; - // This only happens for auto-hide. When the panel is closed from within - // itself, doneCallback removes the listener and only then hides the popup - gAddBookmarksPanel.saveItem(); - gAddBookmarksPanel.uninitPanel(); + gEditItemOverlay.uninitPanel(true); }, _overlayLoaded: false, @@ -96,23 +93,15 @@ var PlacesCommandHook = { this._overlayLoading = true; document.loadOverlay("chrome://browser/content/places/editBookmarkOverlay.xul", loadObserver); + this.panel.addEventListener("popuphiding", this, false); }, _doShowEditBookmarkPanel: function PCH__doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition) { - var panel = this.panel; - panel.openPopup(aAnchorElement, aPosition, -1, -1); + this.panel.openPopup(aAnchorElement, aPosition, -1, -1); - gAddBookmarksPanel.initPanel(aItemId, PlacesUtils.tm, this.doneCallback, - { hiddenRows: "description" }); - panel.addEventListener("popuphiding", this, false); - }, - - doneCallback: function PCH_doneCallback(aSavedChanges) { - var panel = PlacesCommandHook.panel; - panel.removeEventListener("popuphiding", PlacesCommandHook, false); - gAddBookmarksPanel.uninitPanel(); - panel.hidePopup(); + gEditItemOverlay.initPanel(aItemId, + { hiddenRows: ["description", "location"] }); }, /** @@ -289,6 +278,19 @@ var PlacesCommandHook = { organizer.focus(); } + }, + + doneButtonOnCommand: function PCH_doneButtonOnCommand() { + this.panel.hidePopup(); + }, + + deleteButtonOnCommand: function PCH_deleteButtonCommand() { + PlacesUtils.bookmarks.removeItem(gEditItemOverlay.itemId); + + // remove all tags for the associated url + PlacesUtils.tagging.untagURI(gEditItemOverlay._uri, null); + + this.panel.hidePopup(); } }; diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index 7fbf7720c1de..bdb10ae57753 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -28,3 +28,42 @@ menuitem.spell-suggestion { window[sizemode="maximized"] #content .notification-inner { border-right: 0px !important; } + +tabbrowser { + -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser"); +} + +.tabbrowser-tabs { + -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabs"); +} + +.tabbrowser-arrowscrollbox { + -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-arrowscrollbox"); +} + +.tabs-alltabs-popup { + -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup"); +} + +.tabbrowser-tabs > .tabbrowser-tab { + -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab"); +} + +.tabbrowser-tabs > .tabbrowser-tab > .tab-close-button, +.tabbrowser-tabs .tabs-closebutton-box > .tabs-closebutton { + -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-close-tab-button"); +} + +.tab-close-button { + display: none; +} + +.tabbrowser-tabs:not([closebuttons="noclose"]):not([closebuttons="closeatend"]) > .tabbrowser-tab[selected="true"] > .tab-close-button { + display: -moz-box; +} + +.tabbrowser-tabs[closebuttons="alltabs"] > .tabbrowser-tab > .tab-close-button { + display: -moz-box; +} + + diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 57eaded043d2..db24b379ea03 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -99,6 +99,7 @@ var gProgressCollapseTimer = null; var gPrefService = null; var appCore = null; var gBrowser = null; +var gNavToolbox = null; var gSidebarCommand = ""; // Global variable that holds the nsContextMenu instance. @@ -1029,8 +1030,7 @@ function delayedStartup() SetPageProxyState("invalid"); - var toolbox = document.getElementById("navigator-toolbox"); - toolbox.customizeDone = BrowserToolboxCustomizeDone; + getNavToolbox().customizeDone = BrowserToolboxCustomizeDone; // Set up Sanitize Item gSanitizeListener = new SanitizeListener(); @@ -1114,6 +1114,25 @@ function delayedStartup() Components.utils.reportError("Failed to init content pref service:\n" + ex); } +#ifdef XP_WIN + // For Vista, flip the default download folder pref once from Desktop to Downloads + // on new profiles. + try { + var sysInfo = Cc["@mozilla.org/system-info;1"]. + getService(Ci.nsIPropertyBag2); + if (parseFloat(sysInfo.getProperty("version")) >= 6 && + !gPrefService.getPrefType("browser.download.dir") && + gPrefService.getIntPref("browser.download.folderList") == 0) { + var dnldMgr = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + gPrefService.setCharPref("browser.download.dir", + dnldMgr.defaultDownloadsDirectory.path); + gPrefService.setIntPref("browser.download.folderList", 1); + } + } catch (ex) { + } +#endif + // initialize the session-restore service (in case it's not already running) if (document.documentElement.getAttribute("windowtype") == "navigator:browser") { try { @@ -2260,13 +2279,13 @@ function toggleAffectedChrome(aHide) // (*) menubar // (*) navigation bar // (*) bookmarks toolbar + // (*) tabstrip // (*) browser messages // (*) sidebar // (*) find bar // (*) statusbar - var navToolbox = document.getElementById("navigator-toolbox"); - navToolbox.hidden = aHide; + getNavToolbox().hidden = aHide; if (aHide) { gChromeState = {}; @@ -2274,6 +2293,9 @@ function toggleAffectedChrome(aHide) gChromeState.sidebarOpen = !sidebar.hidden; gSidebarCommand = sidebar.getAttribute("sidebarcommand"); + gChromeState.hadTabStrip = gBrowser.getStripVisibility(); + gBrowser.setStripVisibilityTo(false); + var notificationBox = gBrowser.getNotificationBox(); gChromeState.notificationsOpen = !notificationBox.notificationsHidden; notificationBox.notificationsHidden = aHide; @@ -2286,6 +2308,10 @@ function toggleAffectedChrome(aHide) gFindBar.close(); } else { + if (gChromeState.hadTabStrip) { + gBrowser.setStripVisibilityTo(true); + } + if (gChromeState.notificationsOpen) { gBrowser.getNotificationBox().notificationsHidden = aHide; } @@ -2314,6 +2340,11 @@ function onExitPrintPreview() toggleAffectedChrome(false); } +function getPPBrowser() +{ + return getBrowser(); +} + function getMarkupDocumentViewer() { return gBrowser.markupDocumentViewer; @@ -3030,7 +3061,7 @@ function BrowserCustomizeToolbar() window.openDialog("chrome://global/content/customizeToolbar.xul", "CustomizeToolbar", "chrome,all,dependent", - document.getElementById("navigator-toolbox")); + getNavToolbox()); #endif } @@ -3154,7 +3185,7 @@ var FullScreen = } } - var toolbox = document.getElementById("navigator-toolbox"); + var toolbox = getNavToolbox(); if (aShow) toolbox.removeAttribute("inFullscreen"); else @@ -3853,7 +3884,7 @@ function onViewToolbarsPopupShowing(aEvent) var firstMenuItem = popup.firstChild; - var toolbox = document.getElementById("navigator-toolbox"); + var toolbox = getNavToolbox(); for (i = 0; i < toolbox.childNodes.length; ++i) { var toolbar = toolbox.childNodes[i]; var toolbarName = toolbar.getAttribute("toolbarname"); @@ -3875,7 +3906,7 @@ function onViewToolbarsPopupShowing(aEvent) function onViewToolbarCommand(aEvent) { - var toolbox = document.getElementById("navigator-toolbox"); + var toolbox = getNavToolbox(); var index = aEvent.originalTarget.getAttribute("toolbarindex"); var toolbar = toolbox.childNodes[index]; @@ -4367,7 +4398,6 @@ var contentAreaDNDObserver = { }; -// For extensions function getBrowser() { if (!gBrowser) @@ -4375,6 +4405,13 @@ function getBrowser() return gBrowser; } +function getNavToolbox() +{ + if (!gNavToolbox) + gNavToolbox = document.getElementById("navigator-toolbox"); + return gNavToolbox; +} + function MultiplexHandler(event) { try { var node = event.target; diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index 8e1544d56d57..753fb9143524 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -75,7 +75,7 @@ # so that they can be shared by macBrowserOverlay.xul. #include global-scripts.inc + + + + + + + (anonumous content) + + (anonumous content) + + + + + ++window.constructorFired; + document.getAnonymousNodes(this)[0].addEventListener( + "DOMCharacterDataModified", + function(evt) { + ++window.characterdatamodified; + }, + true); + + + + + + +Mozilla Bug 391568 +

+
+ (real content) + (real content) +
+
+
+
+ + + diff --git a/content/html/content/src/Makefile.in b/content/html/content/src/Makefile.in index a6edfef3d105..8bae404ce80b 100644 --- a/content/html/content/src/Makefile.in +++ b/content/html/content/src/Makefile.in @@ -49,6 +49,7 @@ LIBXUL_LIBRARY = 1 REQUIRES = xpcom \ string \ gfx \ + thebes \ layout \ widget \ dom \ diff --git a/content/html/document/src/Makefile.in b/content/html/document/src/Makefile.in index 23ac37343a3c..51dfe3013a08 100644 --- a/content/html/document/src/Makefile.in +++ b/content/html/document/src/Makefile.in @@ -49,6 +49,7 @@ LIBXUL_LIBRARY = 1 REQUIRES = xpcom \ string \ gfx \ + thebes \ layout \ widget \ dom \ diff --git a/content/html/document/src/nsHTMLContentSink.cpp b/content/html/document/src/nsHTMLContentSink.cpp index c9a57c92f967..90e0a08cbcf9 100644 --- a/content/html/document/src/nsHTMLContentSink.cpp +++ b/content/html/document/src/nsHTMLContentSink.cpp @@ -3199,13 +3199,13 @@ HTMLContentSink::FlushPendingNotifications(mozFlushType aType) // Only flush tags if we're not doing the notification ourselves // (since we aren't reentrant) if (mCurrentContext && !mInNotification) { - if (aType & Flush_SinkNotifications) { + if (aType >= Flush_ContentAndNotify) { mCurrentContext->FlushTags(); } else { mCurrentContext->FlushText(); } - if (aType & Flush_OnlyReflow) { + if (aType >= Flush_Layout) { // Make sure that layout has started so that the reflow flush // will actually happen. StartLayout(PR_TRUE); diff --git a/content/svg/document/src/Makefile.in b/content/svg/document/src/Makefile.in index f8126868421f..845afae278bd 100644 --- a/content/svg/document/src/Makefile.in +++ b/content/svg/document/src/Makefile.in @@ -51,6 +51,7 @@ REQUIRES = content \ xpcom \ string \ gfx \ + thebes \ dom \ webshell \ htmlparser \ diff --git a/content/xbl/src/Makefile.in b/content/xbl/src/Makefile.in index 6de22a5832d4..3c4986dc7ef1 100644 --- a/content/xbl/src/Makefile.in +++ b/content/xbl/src/Makefile.in @@ -51,6 +51,7 @@ REQUIRES = xpcom \ js \ dom \ gfx \ + thebes \ layout \ xultmpl \ widget \ diff --git a/content/xbl/src/nsBindingManager.cpp b/content/xbl/src/nsBindingManager.cpp index e0d8bcb4c2db..eb6cfd18bddb 100644 --- a/content/xbl/src/nsBindingManager.cpp +++ b/content/xbl/src/nsBindingManager.cpp @@ -263,14 +263,16 @@ AddObjectEntry(PLDHashTable& table, nsISupports* aKey, nsISupports* aValue) // helper routine for looking up an existing entry. Note that the // return result is NOT addreffed static nsISupports* -LookupObject(PLDHashTable& table, nsISupports* aKey) +LookupObject(PLDHashTable& table, nsIContent* aKey) { - ObjectEntry *entry = - static_cast - (PL_DHashTableOperate(&table, aKey, PL_DHASH_LOOKUP)); + if (aKey && aKey->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) { + ObjectEntry *entry = + static_cast + (PL_DHashTableOperate(&table, aKey, PL_DHASH_LOOKUP)); - if (PL_DHASH_ENTRY_IS_BUSY(entry)) - return entry->GetValue(); + if (PL_DHASH_ENTRY_IS_BUSY(entry)) + return entry->GetValue(); + } return nsnull; } @@ -843,36 +845,38 @@ nsBindingManager::ProcessAttachedQueue() mAttachedStack.Compact(); } +// Keep bindings and bound elements alive while executing detached handlers. +struct BindingTableReadClosure +{ + nsCOMArray mBoundElements; + nsBindingList mBindings; +}; + PR_STATIC_CALLBACK(PLDHashOperator) AccumulateBindingsToDetach(nsISupports *aKey, nsXBLBinding *aBinding, - void* aVoidArray) -{ - nsVoidArray* arr = static_cast(aVoidArray); - // Hold an owning reference to this binding, just in case - if (arr->AppendElement(aBinding)) - NS_ADDREF(aBinding); + void* aClosure) + { + BindingTableReadClosure* closure = + static_cast(aClosure); + if (aBinding && closure->mBindings.AppendElement(aBinding)) { + if (!closure->mBoundElements.AppendObject(aBinding->GetBoundElement())) { + closure->mBindings.RemoveElementAt(closure->mBindings.Length() - 1); + } + } return PL_DHASH_NEXT; } -PR_STATIC_CALLBACK(PRBool) -ExecuteDetachedHandler(void* aBinding, void* aClosure) -{ - NS_PRECONDITION(aBinding, "Null binding in list?"); - nsXBLBinding* binding = static_cast(aBinding); - binding->ExecuteDetachedHandler(); - // Drop our ref to the binding now - NS_RELEASE(binding); - return PR_TRUE; -} - void nsBindingManager::ExecuteDetachedHandlers() { // Walk our hashtable of bindings. if (mBindingTable.IsInitialized()) { - nsVoidArray bindingsToDetach; - mBindingTable.EnumerateRead(AccumulateBindingsToDetach, &bindingsToDetach); - bindingsToDetach.EnumerateForwards(ExecuteDetachedHandler, nsnull); + BindingTableReadClosure closure; + mBindingTable.EnumerateRead(AccumulateBindingsToDetach, &closure); + PRUint32 i, count = closure.mBindings.Length(); + for (i = 0; i < count; ++i) { + closure.mBindings[i]->ExecuteDetachedHandler(); + } } } diff --git a/content/xml/content/src/Makefile.in b/content/xml/content/src/Makefile.in index e7dd530b3681..8c0e7109d6ea 100644 --- a/content/xml/content/src/Makefile.in +++ b/content/xml/content/src/Makefile.in @@ -51,6 +51,7 @@ REQUIRES = xpcom \ js \ dom \ gfx \ + thebes \ layout \ widget \ necko \ diff --git a/content/xml/document/src/Makefile.in b/content/xml/document/src/Makefile.in index e8c56bcbc926..d77e0175f671 100644 --- a/content/xml/document/src/Makefile.in +++ b/content/xml/document/src/Makefile.in @@ -51,6 +51,7 @@ REQUIRES = xpcom \ js \ dom \ gfx \ + thebes \ layout \ widget \ caps \ diff --git a/content/xml/document/src/nsXMLContentSink.cpp b/content/xml/document/src/nsXMLContentSink.cpp index e81d8fd0db34..1d95815a09a1 100644 --- a/content/xml/document/src/nsXMLContentSink.cpp +++ b/content/xml/document/src/nsXMLContentSink.cpp @@ -1579,13 +1579,13 @@ nsXMLContentSink::FlushPendingNotifications(mozFlushType aType) // Only flush tags if we're not doing the notification ourselves // (since we aren't reentrant) if (!mInNotification) { - if (aType & Flush_SinkNotifications) { + if (aType >= Flush_ContentAndNotify) { FlushTags(); } else { FlushText(); } - if (aType & Flush_OnlyReflow) { + if (aType >= Flush_Layout) { // Make sure that layout has started so that the reflow flush // will actually happen. MaybeStartLayout(PR_TRUE); diff --git a/content/xtf/src/Makefile.in b/content/xtf/src/Makefile.in index 2d53a809fcff..7df385963170 100644 --- a/content/xtf/src/Makefile.in +++ b/content/xtf/src/Makefile.in @@ -50,6 +50,7 @@ REQUIRES = xpcom \ layout \ widget \ gfx \ + thebes \ dom \ js \ locale \ diff --git a/content/xul/content/src/Makefile.in b/content/xul/content/src/Makefile.in index 8b21de9ef0a8..2bb29d4903fa 100644 --- a/content/xul/content/src/Makefile.in +++ b/content/xul/content/src/Makefile.in @@ -52,6 +52,7 @@ endif REQUIRES = xpcom \ string \ gfx \ + thebes \ layout \ content \ widget \ diff --git a/content/xul/document/src/Makefile.in b/content/xul/document/src/Makefile.in index 8151b22f7edf..c1c5d6ee22a9 100644 --- a/content/xul/document/src/Makefile.in +++ b/content/xul/document/src/Makefile.in @@ -51,6 +51,7 @@ REQUIRES = xpcom \ exthandler \ mimetype \ gfx \ + thebes \ layout \ content \ widget \ diff --git a/content/xul/templates/src/Makefile.in b/content/xul/templates/src/Makefile.in index e88e5d1becee..34b85e3911bb 100644 --- a/content/xul/templates/src/Makefile.in +++ b/content/xul/templates/src/Makefile.in @@ -52,6 +52,7 @@ REQUIRES = xpcom \ xpconnect \ caps \ gfx \ + thebes \ layout \ content \ pref \ diff --git a/docshell/base/Makefile.in b/docshell/base/Makefile.in index 73802184bae3..fd1074642aba 100644 --- a/docshell/base/Makefile.in +++ b/docshell/base/Makefile.in @@ -56,6 +56,7 @@ REQUIRES = xpcom \ caps \ necko \ gfx \ + thebes \ layout \ content \ dom \ diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 626fc8cb7a52..a8d1ceb0a5e4 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -5037,6 +5037,12 @@ nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal) (void) FirePageHideNotification(!mSavingOldViewer); } + // Now make sure we don't think we're in the middle of firing unload after + // this point. This will make us fire unload when the about:blank document + // unloads... but that's ok, more or less. Would be nice if it fired load + // too, of course. + mFiredUnloadEvent = PR_FALSE; + // one helper factory, please nsCOMPtr catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID)); if (!catMan) diff --git a/docshell/build/Makefile.in b/docshell/build/Makefile.in index bff9e639cbf5..a018bc2fd6ec 100644 --- a/docshell/build/Makefile.in +++ b/docshell/build/Makefile.in @@ -62,6 +62,7 @@ REQUIRES = xpcom \ necko \ nkcache \ gfx \ + thebes \ content \ layout \ webshell \ diff --git a/dom/src/base/Makefile.in b/dom/src/base/Makefile.in index 17ffabe81ca5..596954a5500f 100644 --- a/dom/src/base/Makefile.in +++ b/dom/src/base/Makefile.in @@ -53,6 +53,7 @@ REQUIRES = xpcom \ js \ widget \ gfx \ + thebes \ layout \ content \ caps \ diff --git a/dom/src/offline/Makefile.in b/dom/src/offline/Makefile.in index 80732a3b38a4..127e93d9f3ac 100644 --- a/dom/src/offline/Makefile.in +++ b/dom/src/offline/Makefile.in @@ -51,6 +51,7 @@ REQUIRES = xpcom \ content \ caps \ gfx \ + thebes \ js \ layout \ locale \ diff --git a/dom/src/storage/Makefile.in b/dom/src/storage/Makefile.in index de6100f08012..5f35765eaa17 100644 --- a/dom/src/storage/Makefile.in +++ b/dom/src/storage/Makefile.in @@ -51,6 +51,7 @@ REQUIRES = xpcom \ content \ caps \ gfx \ + thebes \ js \ layout \ locale \ diff --git a/dom/src/storage/nsDOMStorage.cpp b/dom/src/storage/nsDOMStorage.cpp index aa753d43f074..e7b1ca8a1fcf 100644 --- a/dom/src/storage/nsDOMStorage.cpp +++ b/dom/src/storage/nsDOMStorage.cpp @@ -162,6 +162,9 @@ nsDOMStorageManager::Shutdown() { NS_IF_RELEASE(gStorageManager); gStorageManager = nsnull; + + delete nsDOMStorage::gStorageDB; + nsDOMStorage::gStorageDB = nsnull; } PR_STATIC_CALLBACK(PLDHashOperator) diff --git a/editor/composer/src/Makefile.in b/editor/composer/src/Makefile.in index d6beb0705e94..6202777b0fea 100644 --- a/editor/composer/src/Makefile.in +++ b/editor/composer/src/Makefile.in @@ -65,6 +65,7 @@ REQUIRES = xpcom \ webshell \ docshell \ gfx \ + thebes \ widget \ xuldoc \ txtsvc \ diff --git a/editor/libeditor/base/Makefile.in b/editor/libeditor/base/Makefile.in index 3ca776f655b6..4059045e07fd 100644 --- a/editor/libeditor/base/Makefile.in +++ b/editor/libeditor/base/Makefile.in @@ -59,6 +59,7 @@ REQUIRES = xpcom \ pref \ view \ gfx \ + thebes \ widget \ unicharutil \ commandhandler \ diff --git a/editor/libeditor/text/Makefile.in b/editor/libeditor/text/Makefile.in index ba6f9022d934..a15e89ecbc1a 100644 --- a/editor/libeditor/text/Makefile.in +++ b/editor/libeditor/text/Makefile.in @@ -59,6 +59,7 @@ REQUIRES = xpcom \ pref \ lwbrk \ gfx \ + thebes \ widget \ unicharutil \ $(NULL) diff --git a/editor/txtsvc/src/Makefile.in b/editor/txtsvc/src/Makefile.in index 8bfbfa4f24d6..a38d2d6b010d 100644 --- a/editor/txtsvc/src/Makefile.in +++ b/editor/txtsvc/src/Makefile.in @@ -52,6 +52,7 @@ REQUIRES = xpcom \ string \ editor \ gfx \ + thebes \ layout \ content \ dom \ diff --git a/embedding/browser/gtk/src/Makefile.in b/embedding/browser/gtk/src/Makefile.in index 846b49824212..532b3b6bcb5d 100644 --- a/embedding/browser/gtk/src/Makefile.in +++ b/embedding/browser/gtk/src/Makefile.in @@ -86,6 +86,7 @@ REQUIRES = xpcom \ widget \ dom \ gfx \ + thebes \ intl \ imglib2 \ layout \ diff --git a/embedding/browser/webBrowser/Makefile.in b/embedding/browser/webBrowser/Makefile.in index e352baf9a3bd..4fcdd6c80750 100644 --- a/embedding/browser/webBrowser/Makefile.in +++ b/embedding/browser/webBrowser/Makefile.in @@ -54,6 +54,7 @@ REQUIRES = xpcom \ docshell \ widget \ gfx \ + thebes \ layout \ content \ dom \ diff --git a/embedding/components/find/src/Makefile.in b/embedding/components/find/src/Makefile.in index f963b265b548..7ff8d49ac924 100644 --- a/embedding/components/find/src/Makefile.in +++ b/embedding/components/find/src/Makefile.in @@ -51,6 +51,7 @@ REQUIRES = string \ dom \ docshell \ gfx \ + thebes \ layout \ editor \ locale \ diff --git a/embedding/components/windowwatcher/src/nsPrompt.cpp b/embedding/components/windowwatcher/src/nsPrompt.cpp index 1358924fa424..562e84f9ba9a 100644 --- a/embedding/components/windowwatcher/src/nsPrompt.cpp +++ b/embedding/components/windowwatcher/src/nsPrompt.cpp @@ -46,11 +46,6 @@ #include "nsIStringBundle.h" #include "nsIChannel.h" #include "nsIURI.h" -#include "nsIDOMDocument.h" -#include "nsIDOMDocumentEvent.h" -#include "nsIDOMEventTarget.h" -#include "nsIDOMEvent.h" -#include "nsIPrivateDOMEvent.h" #include "nsEmbedCID.h" #include "nsNetCID.h" #include "nsPIDOMWindow.h" @@ -176,80 +171,10 @@ nsPrompt::Init() // nsPrompt::nsIPrompt //***************************************************************************** -nsAutoWindowStateHelper::nsAutoWindowStateHelper(nsIDOMWindow *aWindow) - : mWindow(aWindow), - mDefaultEnabled(DispatchCustomEvent("DOMWillOpenModalDialog")) -{ - nsCOMPtr window(do_QueryInterface(aWindow)); - - if (window) { - window->EnterModalState(); - } -} - -nsAutoWindowStateHelper::~nsAutoWindowStateHelper() -{ - nsCOMPtr window(do_QueryInterface(mWindow)); - - if (window) { - window->LeaveModalState(); - } - - if (mDefaultEnabled) { - DispatchCustomEvent("DOMModalDialogClosed"); - } -} - -PRBool -nsAutoWindowStateHelper::DispatchCustomEvent(const char *aEventName) -{ - if (!mWindow) { - return PR_TRUE; - } - -#ifdef DEBUG - { - nsCOMPtr window(do_QueryInterface(mWindow)); - } -#endif - - nsCOMPtr domdoc; - mWindow->GetDocument(getter_AddRefs(domdoc)); - - nsCOMPtr docevent(do_QueryInterface(domdoc)); - nsCOMPtr event; - - PRBool defaultActionEnabled = PR_TRUE; - - if (docevent) { - docevent->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event)); - - nsCOMPtr privateEvent(do_QueryInterface(event)); - if (privateEvent) { - event->InitEvent(NS_ConvertASCIItoUTF16(aEventName), PR_TRUE, PR_TRUE); - - privateEvent->SetTrusted(PR_TRUE); - - nsCOMPtr target(do_QueryInterface(mWindow)); - - target->DispatchEvent(event, &defaultActionEnabled); - } - } - - return defaultActionEnabled; -} - - NS_IMETHODIMP nsPrompt::Alert(const PRUnichar* dialogTitle, const PRUnichar* text) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - return NS_OK; - } - return mPromptService->Alert(mParent, dialogTitle, text); } @@ -259,13 +184,6 @@ nsPrompt::AlertCheck(const PRUnichar* dialogTitle, const PRUnichar* checkMsg, PRBool *checkValue) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // checkValue is an inout parameter, so we don't have to set it - return NS_OK; - } - return mPromptService->AlertCheck(mParent, dialogTitle, text, checkMsg, checkValue); } @@ -275,14 +193,6 @@ nsPrompt::Confirm(const PRUnichar* dialogTitle, const PRUnichar* text, PRBool *_retval) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // Default to cancel - *_retval = PR_FALSE; - return NS_OK; - } - return mPromptService->Confirm(mParent, dialogTitle, text, _retval); } @@ -293,14 +203,6 @@ nsPrompt::ConfirmCheck(const PRUnichar* dialogTitle, PRBool *checkValue, PRBool *_retval) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // Default to cancel. checkValue is an inout parameter, so we don't have to set it - *_retval = PR_FALSE; - return NS_OK; - } - return mPromptService->ConfirmCheck(mParent, dialogTitle, text, checkMsg, checkValue, _retval); } @@ -316,16 +218,6 @@ nsPrompt::ConfirmEx(const PRUnichar *dialogTitle, PRBool *checkValue, PRInt32 *buttonPressed) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // Return 1 to match what happens when the dialog is closed by the window - // manager (This is indeed independent of what the default button is). - // checkValue is an inout parameter, so we don't have to set it. - *buttonPressed = 1; - return NS_OK; - } - return mPromptService->ConfirmEx(mParent, dialogTitle, text, buttonFlags, button0Title, button1Title, button2Title, checkMsg, checkValue, buttonPressed); @@ -339,15 +231,6 @@ nsPrompt::Prompt(const PRUnichar *dialogTitle, PRBool *checkValue, PRBool *_retval) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // Default to cancel. answer and checkValue are inout parameters, so we - // don't have to set them. - *_retval = PR_FALSE; - return NS_OK; - } - return mPromptService->Prompt(mParent, dialogTitle, text, answer, checkMsg, checkValue, _retval); } @@ -361,15 +244,6 @@ nsPrompt::PromptUsernameAndPassword(const PRUnichar *dialogTitle, PRBool *checkValue, PRBool *_retval) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // Default to cancel. username, password and checkValue are inout - // parameters, so we don't have to set them. - *_retval = PR_FALSE; - return NS_OK; - } - return mPromptService->PromptUsernameAndPassword(mParent, dialogTitle, text, username, password, checkMsg, checkValue, @@ -384,15 +258,6 @@ nsPrompt::PromptPassword(const PRUnichar *dialogTitle, PRBool *checkValue, PRBool *_retval) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // Default to cancel. password and checkValue are inout parameters, so we - // don't have to touch them. - *_retval = PR_FALSE; - return NS_OK; - } - return mPromptService->PromptPassword(mParent, dialogTitle, text, password, checkMsg, checkValue, _retval); } @@ -405,15 +270,6 @@ nsPrompt::Select(const PRUnichar *dialogTitle, PRInt32 *outSelection, PRBool *_retval) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // Default to cancel and item 0 - *outSelection = 0; - *_retval = PR_FALSE; - return NS_OK; - } - return mPromptService->Select(mParent, dialogTitle, inMsg, inCount, inList, outSelection, _retval); } @@ -433,15 +289,6 @@ nsPrompt::Prompt(const PRUnichar* dialogTitle, PRUnichar* *result, PRBool *_retval) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // Default to cancel - *result = nsnull; - *_retval = PR_FALSE; - return NS_OK; - } - // Ignore passwordRealm and savePassword if (defaultText) { *result = ToNewUnicode(nsDependentString(defaultText)); @@ -464,16 +311,6 @@ nsPrompt::PromptUsernameAndPassword(const PRUnichar* dialogTitle, PRUnichar* *pwd, PRBool *_retval) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // Default to cancel - *user = nsnull; - *pwd = nsnull; - *_retval = PR_FALSE; - return NS_OK; - } - // Ignore passwordRealm and savePassword return mPromptService->PromptUsernameAndPassword(mParent, dialogTitle, text, user, pwd, nsnull, nsnull, @@ -488,15 +325,6 @@ nsPrompt::PromptPassword(const PRUnichar* dialogTitle, PRUnichar* *pwd, PRBool *_retval) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // Default to cancel - *pwd = nsnull; - *_retval = PR_FALSE; - return NS_OK; - } - // Ignore passwordRealm and savePassword return mPromptService->PromptPassword(mParent, dialogTitle, text, pwd, nsnull, nsnull, _retval); @@ -507,14 +335,6 @@ nsPrompt::PromptAuth(nsIChannel* aChannel, nsIAuthInformation* aAuthInfo, PRBool* retval) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // Default to cancel - *retval = PR_FALSE; - return NS_OK; - } - if (mPromptService2) { return mPromptService2->PromptAuth(mParent, aChannel, aLevel, aAuthInfo, @@ -533,13 +353,6 @@ nsPrompt::AsyncPromptAuth(nsIChannel* aChannel, nsIAuthInformation* aAuthInfo, nsICancelable** retval) { - nsAutoWindowStateHelper windowStateHelper(mParent); - - if (!windowStateHelper.DefaultEnabled()) { - // XXX what to do? - return NS_ERROR_NOT_IMPLEMENTED; - } - if (mPromptService2) { return mPromptService2->AsyncPromptAuth(mParent, aChannel, aCallback, aContext, diff --git a/embedding/components/windowwatcher/src/nsPrompt.h b/embedding/components/windowwatcher/src/nsPrompt.h index 50eb7c64a043..fbac461afb36 100644 --- a/embedding/components/windowwatcher/src/nsPrompt.h +++ b/embedding/components/windowwatcher/src/nsPrompt.h @@ -79,30 +79,6 @@ protected: nsCOMPtr mPromptService2; }; - -/** - * Helper class for dealing with notifications around opening modal - * windows. - */ -class nsAutoWindowStateHelper -{ -public: - nsAutoWindowStateHelper(nsIDOMWindow *aWindow); - ~nsAutoWindowStateHelper(); - - PRBool DefaultEnabled() - { - return mDefaultEnabled; - } - -protected: - PRBool DispatchCustomEvent(const char *aEventName); - - nsIDOMWindow *mWindow; - PRBool mDefaultEnabled; -}; - - /** * A class that wraps an nsIAuthPrompt so that it can be used as an * nsIAuthPrompt2. diff --git a/embedding/components/windowwatcher/src/nsPromptService.cpp b/embedding/components/windowwatcher/src/nsPromptService.cpp index bf19e0bbeb86..809d2829391d 100644 --- a/embedding/components/windowwatcher/src/nsPromptService.cpp +++ b/embedding/components/windowwatcher/src/nsPromptService.cpp @@ -42,6 +42,11 @@ #include "nsIComponentManager.h" #include "nsIDialogParamBlock.h" #include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMEventTarget.h" +#include "nsIDOMEvent.h" +#include "nsIPrivateDOMEvent.h" +#include "nsIDOMDocumentEvent.h" #include "nsIServiceManager.h" #include "nsISupportsUtils.h" #include "nsString.h" @@ -57,6 +62,75 @@ static const char kWarningIconClass[] = "message-icon"; #define kCommonDialogsProperties "chrome://global/locale/commonDialogs.properties" +/**************************************************************** + ****************** nsAutoWindowStateHelper ********************* + ****************************************************************/ + +nsAutoWindowStateHelper::nsAutoWindowStateHelper(nsIDOMWindow *aWindow) + : mWindow(aWindow), + mDefaultEnabled(DispatchCustomEvent("DOMWillOpenModalDialog")) +{ + nsCOMPtr window(do_QueryInterface(aWindow)); + + if (window) { + window->EnterModalState(); + } +} + +nsAutoWindowStateHelper::~nsAutoWindowStateHelper() +{ + nsCOMPtr window(do_QueryInterface(mWindow)); + + if (window) { + window->LeaveModalState(); + } + + if (mDefaultEnabled) { + DispatchCustomEvent("DOMModalDialogClosed"); + } +} + +PRBool +nsAutoWindowStateHelper::DispatchCustomEvent(const char *aEventName) +{ + if (!mWindow) { + return PR_TRUE; + } + +#ifdef DEBUG + { + nsCOMPtr window(do_QueryInterface(mWindow)); + } +#endif + + nsCOMPtr domdoc; + mWindow->GetDocument(getter_AddRefs(domdoc)); + + nsCOMPtr docevent(do_QueryInterface(domdoc)); + nsCOMPtr event; + + PRBool defaultActionEnabled = PR_TRUE; + + if (docevent) { + docevent->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event)); + + nsCOMPtr privateEvent(do_QueryInterface(event)); + if (privateEvent) { + event->InitEvent(NS_ConvertASCIItoUTF16(aEventName), PR_TRUE, PR_TRUE); + + privateEvent->SetTrusted(PR_TRUE); + + nsCOMPtr target(do_QueryInterface(mWindow)); + + target->DispatchEvent(event, &defaultActionEnabled); + } + } + + return defaultActionEnabled; +} + + + /**************************************************************** ************************* ParamBlock *************************** ****************************************************************/ @@ -105,6 +179,12 @@ NS_IMETHODIMP nsPromptService::Alert(nsIDOMWindow *parent, const PRUnichar *dialogTitle, const PRUnichar *text) { + nsAutoWindowStateHelper windowStateHelper(parent); + + if (!windowStateHelper.DefaultEnabled()) { + return NS_OK; + } + nsresult rv; nsXPIDLString stringOwner; @@ -141,6 +221,13 @@ nsPromptService::AlertCheck(nsIDOMWindow *parent, const PRUnichar *checkMsg, PRBool *checkValue) { + nsAutoWindowStateHelper windowStateHelper(parent); + + if (!windowStateHelper.DefaultEnabled()) { + // checkValue is an inout parameter, so we don't have to set it + return NS_OK; + } + nsresult rv; nsXPIDLString stringOwner; @@ -179,6 +266,14 @@ nsPromptService::Confirm(nsIDOMWindow *parent, const PRUnichar *dialogTitle, const PRUnichar *text, PRBool *_retval) { + nsAutoWindowStateHelper windowStateHelper(parent); + + if (!windowStateHelper.DefaultEnabled()) { + // Default to cancel + *_retval = PR_FALSE; + return NS_OK; + } + nsresult rv; nsXPIDLString stringOwner; @@ -219,6 +314,14 @@ nsPromptService::ConfirmCheck(nsIDOMWindow *parent, const PRUnichar *checkMsg, PRBool *checkValue, PRBool *_retval) { + nsAutoWindowStateHelper windowStateHelper(parent); + + if (!windowStateHelper.DefaultEnabled()) { + // Default to cancel. checkValue is an inout parameter, so we don't have to set it + *_retval = PR_FALSE; + return NS_OK; + } + nsresult rv; nsXPIDLString stringOwner; @@ -265,6 +368,16 @@ nsPromptService::ConfirmEx(nsIDOMWindow *parent, const PRUnichar *checkMsg, PRBool *checkValue, PRInt32 *buttonPressed) { + nsAutoWindowStateHelper windowStateHelper(parent); + + if (!windowStateHelper.DefaultEnabled()) { + // Return 1 to match what happens when the dialog is closed by the window + // manager (This is indeed independent of what the default button is). + // checkValue is an inout parameter, so we don't have to set it. + *buttonPressed = 1; + return NS_OK; + } + nsresult rv; nsXPIDLString stringOwner; @@ -368,6 +481,15 @@ nsPromptService::Prompt(nsIDOMWindow *parent, PRUnichar **value, const PRUnichar *checkMsg, PRBool *checkValue, PRBool *_retval) { + nsAutoWindowStateHelper windowStateHelper(parent); + + if (!windowStateHelper.DefaultEnabled()) { + // Default to cancel. value and checkValue are inout parameters, so we + // don't have to set them. + *_retval = PR_FALSE; + return NS_OK; + } + NS_ENSURE_ARG(value); NS_ENSURE_ARG(_retval); @@ -434,7 +556,16 @@ nsPromptService::PromptUsernameAndPassword(nsIDOMWindow *parent, NS_ENSURE_ARG(username); NS_ENSURE_ARG(password); NS_ENSURE_ARG(_retval); - + + nsAutoWindowStateHelper windowStateHelper(parent); + + if (!windowStateHelper.DefaultEnabled()) { + // Default to cancel + // username/password are inout, no need to set them + *_retval = PR_FALSE; + return NS_OK; + } + nsresult rv; nsXPIDLString stringOwner; @@ -504,7 +635,16 @@ NS_IMETHODIMP nsPromptService::PromptPassword(nsIDOMWindow *parent, { NS_ENSURE_ARG(password); NS_ENSURE_ARG(_retval); - + + nsAutoWindowStateHelper windowStateHelper(parent); + + if (!windowStateHelper.DefaultEnabled()) { + // Default to cancel. password and checkValue are inout parameters, so we + // don't have to touch them. + *_retval = PR_FALSE; + return NS_OK; + } + nsresult rv; nsXPIDLString stringOwner; @@ -569,6 +709,13 @@ nsPromptService::PromptAuth(nsIDOMWindow* aParent, PRBool* aCheckValue, PRBool *retval) { + nsAutoWindowStateHelper windowStateHelper(aParent); + + if (!windowStateHelper.DefaultEnabled()) { + *retval = PR_FALSE; + return NS_OK; + } + return nsPrompt::PromptPasswordAdapter(this, aParent, aChannel, aLevel, aAuthInfo, aCheckLabel, aCheckValue, @@ -595,6 +742,15 @@ nsPromptService::Select(nsIDOMWindow *parent, const PRUnichar *dialogTitle, const PRUnichar **selectList, PRInt32 *outSelection, PRBool *_retval) { + nsAutoWindowStateHelper windowStateHelper(parent); + + if (!windowStateHelper.DefaultEnabled()) { + // Default to cancel and item 0 + *outSelection = 0; + *_retval = PR_FALSE; + return NS_OK; + } + nsresult rv; nsXPIDLString stringOwner; diff --git a/embedding/components/windowwatcher/src/nsPromptService.h b/embedding/components/windowwatcher/src/nsPromptService.h index 2e3549fa10c8..6975737e9031 100644 --- a/embedding/components/windowwatcher/src/nsPromptService.h +++ b/embedding/components/windowwatcher/src/nsPromptService.h @@ -77,5 +77,28 @@ private: nsCOMPtr mWatcher; }; +/** + * Helper class for dealing with notifications around opening modal + * windows. + */ +class nsAutoWindowStateHelper +{ +public: + nsAutoWindowStateHelper(nsIDOMWindow *aWindow); + ~nsAutoWindowStateHelper(); + + PRBool DefaultEnabled() + { + return mDefaultEnabled; + } + +protected: + PRBool DispatchCustomEvent(const char *aEventName); + + nsIDOMWindow *mWindow; + PRBool mDefaultEnabled; +}; + + #endif diff --git a/embedding/components/windowwatcher/src/nsWindowWatcher.cpp b/embedding/components/windowwatcher/src/nsWindowWatcher.cpp index d298e1bcc3aa..b41b74cf5498 100644 --- a/embedding/components/windowwatcher/src/nsWindowWatcher.cpp +++ b/embedding/components/windowwatcher/src/nsWindowWatcher.cpp @@ -45,6 +45,7 @@ #include "nsCRT.h" #include "nsNetUtil.h" #include "nsPrompt.h" +#include "nsPromptService.h" #include "nsWWJSUtils.h" #include "plstr.h" diff --git a/extensions/inspector/jar.mn b/extensions/inspector/jar.mn index 5429a3787857..7179862c1722 100644 --- a/extensions/inspector/jar.mn +++ b/extensions/inspector/jar.mn @@ -64,6 +64,8 @@ inspector.jar: content/inspector/prefs/prefsOverlay.xul (resources/content/prefs/prefsOverlay.xul) content/inspector/prefs/pref-sidebar.js (resources/content/prefs/pref-sidebar.js) content/inspector/tests/allskin.xul (resources/content/tests/allskin.xul) + content/inspector/viewers/accessibleEvents/accessibleEvents.js (resources/content/viewers/accessibleEvents/accessibleEvents.js) + content/inspector/viewers/accessibleEvents/accessibleEvents.xul (resources/content/viewers/accessibleEvents/accessibleEvents.xul) content/inspector/viewers/accessibleObject/accessibleObject.js (resources/content/viewers/accessibleObject/accessibleObject.js) content/inspector/viewers/accessibleObject/accessibleObject.xul (resources/content/viewers/accessibleObject/accessibleObject.xul) content/inspector/viewers/accessibleProps/accessibleProps.js (resources/content/viewers/accessibleProps/accessibleProps.js) diff --git a/extensions/inspector/resources/content/res/viewer-registry.rdf b/extensions/inspector/resources/content/res/viewer-registry.rdf index d7a382f2dcae..8e000602f985 100644 --- a/extensions/inspector/resources/content/res/viewer-registry.rdf +++ b/extensions/inspector/resources/content/res/viewer-registry.rdf @@ -100,7 +100,8 @@ !(object instanceof Components.interfaces.nsIDOMNode)) return false; - if (linkedViewer.uid != "accessibleTree" && + if (linkedViewer.uid != "accessibleEvents" && + linkedViewer.uid != "accessibleTree" && (linkedViewer.uid != "dom" || !linkedViewer.getAccessibleNodes())) return false; @@ -126,7 +127,8 @@ !(object instanceof Components.interfaces.nsIDOMNode)) return false; - if (linkedViewer.uid != "accessibleTree" && + if (linkedViewer.uid != "accessibleEvents" && + linkedViewer.uid != "accessibleTree" && (linkedViewer.uid != "dom" || !linkedViewer.getAccessibleNodes())) return false; @@ -143,6 +145,27 @@ + + + + + + (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 ***** */ + +/*************************************************************** +* AccessibleEventsViewer -------------------------------------------- +* The viewer for the accessible events occured on a document accessible. +* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +* REQUIRED IMPORTS: +* chrome://inspector/content/jsutil/xpcom/XPCU.js +****************************************************************/ + +/////////////////////////////////////////////////////////////////////////////// +//// Global Variables + +var viewer; + +/////////////////////////////////////////////////////////////////////////////// +//// Global Constants + +const kObserverServiceCID = "@mozilla.org/observer-service;1"; +const kAccessibleRetrievalCID = "@mozilla.org/accessibleRetrieval;1"; + +const nsIObserverService = Components.interfaces.nsIObserverService; +const nsIAccessibleRetrieval = Components.interfaces.nsIAccessibleRetrieval; +const nsIAccessibleEvent = Components.interfaces.nsIAccessibleEvent; +const nsIAccessNode = Components.interfaces.nsIAccessNode; + +/////////////////////////////////////////////////////////////////////////////// +//// Initialization + +window.addEventListener("load", AccessibleEventsViewer_initialize, false); + +function AccessibleEventsViewer_initialize() +{ + viewer = new AccessibleEventsViewer(); + viewer.initialize(parent.FrameExchange.receiveData(window)); +} + +/////////////////////////////////////////////////////////////////////////////// +//// class AccessibleEventsViewer + +function AccessibleEventsViewer() +{ + this.mURL = window.location; + this.mObsMan = new ObserverManager(this); + + this.mTree = document.getElementById("olAccessibleEvents"); + this.mOlBox = this.mTree.treeBoxObject; +} + +AccessibleEventsViewer.prototype = +{ + // initialization + + mSubject: null, + mPane: null, + mView: null, + + // interface inIViewer + + get uid() { return "accessibleEvents"; }, + get pane() { return this.mPane; }, + get selection() { return this.mSelection; }, + + get subject() { return this.mSubject; }, + set subject(aObject) + { + this.mView = new AccessibleEventsView(aObject); + this.mOlBox.view = this.mView; + this.mObsMan.dispatchEvent("subjectChange", { subject: aObject }); + }, + + initialize: function initialize(aPane) + { + this.mPane = aPane; + aPane.notifyViewerReady(this); + }, + + destroy: function destroy() + { + this.mView.destroy(); + this.mOlBox.view = null; + }, + + isCommandEnabled: function isCommandEnabled(aCommand) + { + return false; + }, + + getCommand: function getCommand(aCommand) + { + return null; + }, + + // event dispatching + + addObserver: function addObserver(aEvent, aObserver) + { + this.mObsMan.addObserver(aEvent, aObserver); + }, + removeObserver: function removeObserver(aEvent, aObserver) + { + this.mObsMan.removeObserver(aEvent, aObserver); + }, + + // utils + + onItemSelected: function onItemSelected() + { + var idx = this.mTree.currentIndex; + this.mSelection = this.mView.getDOMNode(idx); + this.mObsMan.dispatchEvent("selectionChange", + { selection: this.mSelection } ); + }, + + clearEventsList: function clearEventsList() + { + this.mView.clear(); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +//// AccessibleEventsView + +function AccessibleEventsView(aDocument) +{ + this.mDocument = aDocument; + this.mEvents = []; + this.mRowCount = 0; + + this.mAccService = XPCU.getService(kAccessibleRetrievalCID, + nsIAccessibleRetrieval); + + this.mAccDocument = this.mAccService.getAccessibleFor(this.mDocument); + this.mObserverService = XPCU.getService(kObserverServiceCID, + nsIObserverService); + + this.mObserverService.addObserver(this, "accessible-event", false); +} + +AccessibleEventsView.prototype = new inBaseTreeView(); + +AccessibleEventsView.prototype.observe = +function observe(aSubject, aTopic, aData) +{ + var event = XPCU.QI(aSubject, nsIAccessibleEvent); + var accessible = event.accessible; + if (!accessible) + return; + + var accessnode = XPCU.QI(accessible, nsIAccessNode); + var accDocument = accessnode.accessibleDocument; + if (accDocument != this.mAccDocument) + return; + + var type = event.eventType; + var date = new Date(); + var node = accessnode.DOMNode; + + var eventObj = { + event: event, + accessnode: accessnode, + node: node, + nodename: node ? node.nodeName : "", + type: this.mAccService.getStringEventType(type), + time: date.toLocaleTimeString() + }; + + this.mEvents.unshift(eventObj); + ++this.mRowCount; + this.mTree.rowCountChanged(0, 1); +} + +AccessibleEventsView.prototype.destroy = +function destroy() +{ + this.mObserverService.removeObserver(this, "accessible-event"); +} + +AccessibleEventsView.prototype.clear = +function clear() +{ + var count = this.mRowCount; + this.mRowCount = 0; + this.mEvents = []; + this.mTree.rowCountChanged(0, -count); +} + +AccessibleEventsView.prototype.getDOMNode = +function getDOMNode(aRow) +{ + return this.mEvents[aRow].node; +} + +AccessibleEventsView.prototype.getCellText = +function getCellText(aRow, aCol) +{ + if (aCol.id == "olcEventType") + return this.mEvents[aRow].type; + if (aCol.id == "olcEventTime") + return this.mEvents[aRow].time; + if (aCol.id == "olcEventTargetNodeName") + return this.mEvents[aRow].nodename; + return ""; +} + diff --git a/extensions/inspector/resources/content/viewers/accessibleEvents/accessibleEvents.xul b/extensions/inspector/resources/content/viewers/accessibleEvents/accessibleEvents.xul new file mode 100644 index 000000000000..3f8b1bb9b2bf --- /dev/null +++ b/extensions/inspector/resources/content/viewers/accessibleEvents/accessibleEvents.xul @@ -0,0 +1,90 @@ + + + + + %dtd1; + %dtd2; + %dtd3; +]> + + + + + + + + + + + +Mozilla Bug 345267 +

+ + + + + +

+ +
+
+
+ + + diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp index b96e5ffd79fc..b18811da676b 100644 --- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -5061,6 +5061,77 @@ nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator, } } +nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, + line_iterator& aLine, PRBool aInOverflow) + : mFrame(aFrame), mLine(aLine), mInOverflowLines(nsnull) +{ + if (aInOverflow) { + mInOverflowLines = aFrame->GetOverflowLines(); + NS_ASSERTION(mInOverflowLines, "How can we be in overflow if there isn't any?"); + } +} + +PRBool +nsBlockInFlowLineIterator::Next() +{ + ++mLine; + line_iterator end = mInOverflowLines ? mInOverflowLines->end() : mFrame->end_lines(); + if (mLine != end) + return PR_TRUE; + PRBool currentlyInOverflowLines = mInOverflowLines != nsnull; + while (PR_TRUE) { + if (currentlyInOverflowLines) { + mFrame = static_cast(mFrame->GetNextInFlow()); + if (!mFrame) + return PR_FALSE; + mInOverflowLines = nsnull; + mLine = mFrame->begin_lines(); + if (mLine != mFrame->end_lines()) + return PR_TRUE; + } else { + mInOverflowLines = mFrame->GetOverflowLines(); + if (mInOverflowLines) { + mLine = mInOverflowLines->begin(); + NS_ASSERTION(mLine != mInOverflowLines->end(), "empty overflow line list?"); + return PR_TRUE; + } + } + currentlyInOverflowLines = !currentlyInOverflowLines; + } +} + +PRBool +nsBlockInFlowLineIterator::Prev() +{ + line_iterator begin = mInOverflowLines ? mInOverflowLines->begin() : mFrame->begin_lines(); + if (mLine != begin) { + --mLine; + return PR_TRUE; + } + PRBool currentlyInOverflowLines = mInOverflowLines != nsnull; + while (PR_TRUE) { + if (currentlyInOverflowLines) { + mLine = mFrame->end_lines(); + if (mLine != mFrame->begin_lines()) { + --mLine; + return PR_TRUE; + } + } else { + mFrame = static_cast(mFrame->GetPrevInFlow()); + if (!mFrame) + return PR_FALSE; + mInOverflowLines = mFrame->GetOverflowLines(); + if (mInOverflowLines) { + mLine = mInOverflowLines->end(); + NS_ASSERTION(mLine != mInOverflowLines->begin(), "empty overflow line list?"); + --mLine; + return PR_TRUE; + } + } + currentlyInOverflowLines = !currentlyInOverflowLines; + } +} + static nsresult RemoveBlockChild(nsIFrame* aFrame, PRBool aDestroyFrames) { if (!aFrame) @@ -5708,14 +5779,17 @@ nsBlockFrame::PaintTextDecorationLine(nsIRenderingContext& aRenderingContext, PRBool isRTL = visibility->mDirection == NS_STYLE_DIRECTION_RTL; nsRefPtr ctx = (gfxContext*) aRenderingContext.GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT); - gfxFloat a2p = 1.0 / PresContext()->AppUnitsPerDevPixel(); - gfxPoint pt((start + aPt.x) * a2p, (aLine->mBounds.y + aPt.y) * a2p); - gfxSize size(width * a2p, aSize * a2p); - nsCSSRendering::PaintDecorationLine(ctx, aColor, pt, size, - aLine->GetAscent() * a2p, aOffset * a2p, - aSize * a2p, aDecoration, - NS_STYLE_BORDER_STYLE_SOLID, - isRTL); + PRInt32 app = PresContext()->AppUnitsPerDevPixel(); + gfxPoint pt(PresContext()->AppUnitsToGfxUnits(start + aPt.x), + PresContext()->AppUnitsToGfxUnits(aLine->mBounds.y + aPt.y)); + gfxSize size(PresContext()->AppUnitsToGfxUnits(width), + PresContext()->AppUnitsToGfxUnits(aSize)); + nsCSSRendering::PaintDecorationLine( + ctx, aColor, pt, size, + PresContext()->AppUnitsToGfxUnits(aLine->GetAscent()), + PresContext()->AppUnitsToGfxUnits(aOffset), + PresContext()->AppUnitsToGfxUnits(aSize), + aDecoration, NS_STYLE_BORDER_STYLE_SOLID, isRTL); } } diff --git a/layout/generic/nsBlockFrame.h b/layout/generic/nsBlockFrame.h index b50eefdbd38e..5f063cc0e4ce 100644 --- a/layout/generic/nsBlockFrame.h +++ b/layout/generic/nsBlockFrame.h @@ -70,6 +70,7 @@ enum LineReflowStatus { }; class nsBlockReflowState; +class nsBlockInFlowLineIterator; class nsBulletFrame; class nsLineBox; class nsFirstLineFrame; @@ -608,6 +609,7 @@ protected: nsBulletFrame* mBullet; friend class nsBlockReflowState; + friend class nsBlockInFlowLineIterator; private: nsAbsoluteContainingBlock mAbsoluteContainer; @@ -653,5 +655,32 @@ private: }; #endif -#endif /* nsBlockFrame_h___ */ +/** + * Iterates over all lines in the prev-in-flows/next-in-flows of this block. + */ +class nsBlockInFlowLineIterator { +public: + typedef nsBlockFrame::line_iterator line_iterator; + nsBlockInFlowLineIterator(nsBlockFrame* aFrame, line_iterator& aLine, PRBool aInOverflow); + line_iterator GetLine() { return mLine; } + nsBlockFrame* GetContainer() { return mFrame; } + PRBool GetInOverflow() { return mInOverflowLines != nsnull; } + /** + * Returns false if there are no more lines. After this has returned false, + * don't call any methods on this object again. + */ + PRBool Next(); + /** + * Returns false if there are no more lines. After this has returned false, + * don't call any methods on this object again. + */ + PRBool Prev(); + +private: + nsBlockFrame* mFrame; + line_iterator mLine; + nsLineList* mInOverflowLines; +}; + +#endif /* nsBlockFrame_h___ */ diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index 9e7b0b743003..473a97b6e0a1 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -1875,7 +1875,7 @@ nsGfxScrollFrameInner::AsyncScrollPortEvent::Run() { if (mInner) { mInner->mOuter->PresContext()->GetPresShell()-> - FlushPendingNotifications(Flush_OnlyReflow); + FlushPendingNotifications(Flush_Layout); } return mInner ? mInner->FireScrollPortEvent() : NS_OK; } diff --git a/layout/generic/nsHTMLContainerFrame.cpp b/layout/generic/nsHTMLContainerFrame.cpp index b8897ec6c3d9..fcdd49733a2a 100644 --- a/layout/generic/nsHTMLContainerFrame.cpp +++ b/layout/generic/nsHTMLContainerFrame.cpp @@ -214,12 +214,15 @@ nsHTMLContainerFrame::PaintTextDecorationLine( nscoord innerWidth = mRect.width - bp.left - bp.right; nsRefPtr ctx = (gfxContext*) aRenderingContext.GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT); - gfxFloat a2p = 1.0 / PresContext()->AppUnitsPerDevPixel(); - gfxPoint pt((bp.left + aPt.x) * a2p, (bp.top + aPt.y) * a2p); - gfxSize size(innerWidth * a2p, aSize * a2p); - nsCSSRendering::PaintDecorationLine(ctx, aColor, pt, size, aAscent * a2p, - aOffset * a2p, aSize * a2p, aDecoration, - NS_STYLE_BORDER_STYLE_SOLID, isRTL); + gfxPoint pt(PresContext()->AppUnitsToGfxUnits(bp.left + aPt.x), + PresContext()->AppUnitsToGfxUnits(bp.top + aPt.y)); + gfxSize size(PresContext()->AppUnitsToGfxUnits(innerWidth), + PresContext()->AppUnitsToGfxUnits(aSize)); + nsCSSRendering::PaintDecorationLine( + ctx, aColor, pt, size, PresContext()->AppUnitsToGfxUnits(aAscent), + PresContext()->AppUnitsToGfxUnits(aOffset), + PresContext()->AppUnitsToGfxUnits(aSize), + aDecoration, NS_STYLE_BORDER_STYLE_SOLID, isRTL); } void diff --git a/layout/generic/nsInlineFrame.cpp b/layout/generic/nsInlineFrame.cpp index 4a34f8e1ec9f..160760846610 100644 --- a/layout/generic/nsInlineFrame.cpp +++ b/layout/generic/nsInlineFrame.cpp @@ -560,7 +560,7 @@ nsInlineFrame::ReflowFrames(nsPresContext* aPresContext, fm->GetMaxAscent(aMetrics.ascent); fm->GetHeight(aMetrics.height); // Include the text-decoration lines to the height. - // Currently, only undeline is overflowable. + // Currently, only underline is overflowable. nscoord offset, size; fm->GetUnderline(offset, size); nscoord ascentAndUnderline = diff --git a/layout/generic/nsSelection.cpp b/layout/generic/nsSelection.cpp index 385ce7fd3dc3..675a40a66c2d 100644 --- a/layout/generic/nsSelection.cpp +++ b/layout/generic/nsSelection.cpp @@ -212,7 +212,10 @@ public: nsresult ScrollRectIntoView(nsIScrollableView *aScrollableView, nsRect& aRect, PRIntn aVPercent, PRIntn aHPercent, PRBool aScrollParentViews); nsresult PostScrollSelectionIntoViewEvent(SelectionRegion aRegion); - NS_IMETHOD ScrollIntoView(SelectionRegion aRegion=nsISelectionController::SELECTION_FOCUS_REGION, PRBool aIsSynchronous=PR_TRUE); + // aDoFlush only matters if aIsSynchronous is true. If not, we'll just flush + // when the scroll event fires so we make sure to scroll to the right place. + nsresult ScrollIntoView(SelectionRegion aRegion, PRBool aIsSynchronous, + PRBool aDoFlush); nsresult AddItem(nsIDOMRange *aRange); nsresult RemoveItem(nsIDOMRange *aRange); nsresult Clear(nsPresContext* aPresContext); @@ -1219,6 +1222,20 @@ nsFrameSelection::MoveCaret(PRUint32 aKeycode, PRBool aContinueSelection, nsSelectionAmount aAmount) { + { + // Make sure that if our presshell gets Destroy() called when we + // flush we don't die. + nsRefPtr kungFuDeathGrip(this); + + // Flush out layout, since we need it to be up to date to do caret + // positioning. + mShell->FlushPendingNotifications(Flush_Layout); + + if (!mShell) { + return NS_OK; + } + } + nsPresContext *context = mShell->GetPresContext(); if (!context) return NS_ERROR_FAILURE; @@ -1262,7 +1279,9 @@ nsFrameSelection::MoveCaret(PRUint32 aKeycode, } result = mDomSelections[index]->Collapse(weakNodeUsed, offsetused); mHint = HINTRIGHT; - mDomSelections[index]->ScrollIntoView(); + mDomSelections[index]-> + ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, + PR_FALSE, PR_FALSE); return NS_OK; case nsIDOMKeyEvent::DOM_VK_RIGHT : @@ -1277,7 +1296,9 @@ nsFrameSelection::MoveCaret(PRUint32 aKeycode, } result = mDomSelections[index]->Collapse(weakNodeUsed, offsetused); mHint = HINTLEFT; - mDomSelections[index]->ScrollIntoView(); + mDomSelections[index]-> + ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, + PR_FALSE, PR_FALSE); return NS_OK; } } @@ -1426,7 +1447,9 @@ nsFrameSelection::MoveCaret(PRUint32 aKeycode, if (NS_SUCCEEDED(result)) { mHint = tHint; //save the hint parameter now for the next time - result = mDomSelections[index]->ScrollIntoView(); + result = mDomSelections[index]-> + ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, + PR_FALSE, PR_FALSE); } return result; @@ -2538,7 +2561,8 @@ nsFrameSelection::ScrollSelectionIntoView(SelectionType aType, if (!mDomSelections[index]) return NS_ERROR_NULL_POINTER; - return mDomSelections[index]->ScrollIntoView(aRegion, aIsSynchronous); + return mDomSelections[index]->ScrollIntoView(aRegion, aIsSynchronous, + PR_FALSE); } nsresult @@ -5831,7 +5855,8 @@ nsTypedSelection::RemoveRange(nsIDOMRange* aRange) if (cnt > 0) { setAnchorFocusRange(cnt - 1);//reset anchor to LAST range. - ScrollIntoView(); + ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, PR_FALSE, + PR_FALSE); } } if (!mFrameSelection) @@ -7251,7 +7276,7 @@ nsTypedSelection::ScrollSelectionIntoViewEvent::Run() return NS_OK; // event revoked mTypedSelection->mScrollEvent.Forget(); - mTypedSelection->ScrollIntoView(mRegion, PR_TRUE); + mTypedSelection->ScrollIntoView(mRegion, PR_TRUE, PR_TRUE); return NS_OK; } @@ -7273,8 +7298,9 @@ nsTypedSelection::PostScrollSelectionIntoViewEvent(SelectionRegion aRegion) return NS_OK; } -NS_IMETHODIMP -nsTypedSelection::ScrollIntoView(SelectionRegion aRegion, PRBool aIsSynchronous) +nsresult +nsTypedSelection::ScrollIntoView(SelectionRegion aRegion, + PRBool aIsSynchronous, PRBool aDoFlush) { nsresult result; if (!mFrameSelection) @@ -7298,13 +7324,21 @@ nsTypedSelection::ScrollIntoView(SelectionRegion aRegion, PRBool aIsSynchronous) presShell->GetCaret(getter_AddRefs(caret)); if (caret) { - StCaretHider caretHider(caret); // stack-based class hides and shows the caret + // Now that text frame character offsets are always valid (though not + // necessarily correct), the worst that will happen if we don't flush here + // is that some callers might scroll to the wrong place. Those should + // either manually flush if they're in a safe position for it or use the + // async version of this method. + if (aDoFlush) { + presShell->FlushPendingNotifications(Flush_Layout); - // We are going to scroll to a character offset within a frame by - // using APIs on the scrollable view directly. So we need to - // flush out pending reflows to make sure that frames are up-to-date. - // We crash otherwise - bug 252970#c97 - presShell->FlushPendingNotifications(Flush_OnlyReflow); + // Reget the presshell, since it might have gone away. + result = GetPresShell(getter_AddRefs(presShell)); + if (NS_FAILED(result) || !presShell) + return result; + } + + StCaretHider caretHider(caret); // stack-based class hides and shows the caret // // Scroll the selection region into view. diff --git a/layout/generic/nsTextFrameThebes.cpp b/layout/generic/nsTextFrameThebes.cpp index 5a0ec8404681..3381ced583da 100644 --- a/layout/generic/nsTextFrameThebes.cpp +++ b/layout/generic/nsTextFrameThebes.cpp @@ -1104,6 +1104,8 @@ BuildTextRuns(nsIRenderingContext* aRC, nsTextFrame* aForFrame, aLineContainer->QueryInterface(kBlockFrameCID, (void**)&block); if (!block) { + NS_ASSERTION(!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(), + "Breakable non-block line containers not supported"); // Just loop through all the children of the linecontainer ... it's really // just one line scanner.SetAtStartOfLine(); @@ -1151,20 +1153,17 @@ BuildTextRuns(nsIRenderingContext* aRC, nsTextFrame* aForFrame, // but we discard them instead of assigning them to frames. // This is a little awkward because we traverse lines in the reverse direction // but we traverse the frames in each line in the forward direction. - nsBlockFrame::line_iterator firstLine = block->begin_lines(); + nsBlockInFlowLineIterator backIterator(block, line, PR_FALSE); nsTextFrame* stopAtFrame = aForFrame; nsTextFrame* nextLineFirstTextFrame = nsnull; PRBool seenTextRunBoundaryOnLaterLine = PR_FALSE; PRBool mayBeginInTextRun = PR_TRUE; + PRBool inOverflow = PR_FALSE; while (PR_TRUE) { - if (line == firstLine) { - mayBeginInTextRun = PR_FALSE; - break; - } - --line; - PRBool prevLineIsBlock = line->IsBlock(); - ++line; - if (prevLineIsBlock) { + line = backIterator.GetLine(); + block = backIterator.GetContainer(); + inOverflow = backIterator.GetInOverflow(); + if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) { mayBeginInTextRun = PR_FALSE; break; } @@ -1200,7 +1199,6 @@ BuildTextRuns(nsIRenderingContext* aRC, nsTextFrame* aForFrame, if (state.mFirstTextFrame) { nextLineFirstTextFrame = state.mFirstTextFrame; } - --line; } scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun); @@ -1208,19 +1206,20 @@ BuildTextRuns(nsIRenderingContext* aRC, nsTextFrame* aForFrame, // text frames will be accumulated into textRunFrames as we go. When a // text run boundary is required we flush textRunFrames ((re)building their // gfxTextRuns as necessary). - nsBlockFrame::line_iterator endLines = block->end_lines(); - NS_ASSERTION(line != endLines && !line->IsBlock(), "Where is this frame anyway??"); - nsIFrame* child = line->mFirstChild; + nsBlockInFlowLineIterator forwardIterator(block, line, inOverflow); do { + line = forwardIterator.GetLine(); + if (line->IsBlock()) + break; scanner.SetAtStartOfLine(); scanner.SetCommonAncestorWithLastFrame(nsnull); + nsIFrame* child = line->mFirstChild; PRInt32 i; for (i = line->GetChildCount() - 1; i >= 0; --i) { scanner.ScanFrame(child); child = child->GetNextSibling(); } - ++line; - } while (line != endLines && !line->IsBlock()); + } while (forwardIterator.Next()); // Set mStartOfLine so FlushFrames knows its textrun ends a line scanner.SetAtStartOfLine(); @@ -3717,10 +3716,11 @@ FillClippedRect(gfxContext* aCtx, nsPresContext* aPresContext, { gfxRect r = aRect.Intersect(aDirtyRect); // For now, we need to put this in pixel coordinates - float t2p = 1.0f / aPresContext->AppUnitsPerDevPixel(); + PRInt32 app = aPresContext->AppUnitsPerDevPixel(); aCtx->NewPath(); // pixel-snap - aCtx->Rectangle(gfxRect(r.X()*t2p, r.Y()*t2p, r.Width()*t2p, r.Height()*t2p), PR_TRUE); + aCtx->Rectangle(gfxRect(r.X() / app, r.Y() / app, + r.Width() / app, r.Height() / app), PR_TRUE); aCtx->SetColor(gfxRGBA(aColor)); aCtx->Fill(); } @@ -3791,12 +3791,12 @@ nsTextFrame::PaintTextDecorations(gfxContext* aCtx, const gfxRect& aDirtyRect, return; gfxFont::Metrics fontMetrics = GetFontMetrics(aProvider.GetFontGroup()); - gfxFloat a2p = 1.0 / aTextPaintStyle.PresContext()->AppUnitsPerDevPixel(); + PRInt32 app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel(); // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint? - gfxPoint pt(aFramePt.x * a2p, aFramePt.y * a2p); - gfxSize size(GetRect().width * a2p, 0); - gfxFloat ascent = mAscent * a2p; + gfxPoint pt(aFramePt.x / app, aFramePt.y / app); + gfxSize size(GetRect().width / app, 0); + gfxFloat ascent = mAscent / app; if (decorations & NS_FONT_DECORATION_OVERLINE) { size.height = fontMetrics.underlineSize; @@ -4175,13 +4175,13 @@ nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx, gfxFloat advance = hyphenWidth + mTextRun->GetAdvanceWidth(offset, length, &aProvider); if (type == aSelectionType) { - gfxFloat a2p = 1.0 / aTextPaintStyle.PresContext()->AppUnitsPerDevPixel(); + PRInt32 app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel(); // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint? - gfxPoint pt((aTextBaselinePt.x + xOffset) * a2p, - (aTextBaselinePt.y - mAscent) * a2p); - gfxFloat width = PR_ABS(advance) * a2p; + gfxPoint pt((aTextBaselinePt.x + xOffset) / app, + (aTextBaselinePt.y - mAscent) / app); + gfxFloat width = PR_ABS(advance) / app; DrawSelectionDecorations(aCtx, aSelectionType, aTextPaintStyle, - pt, width, mAscent * a2p, decorationMetrics, + pt, width, mAscent / app, decorationMetrics, mTextRun->IsRightToLeft()); } iterator.UpdateWithAdvance(advance); diff --git a/layout/html/tests/Makefile.in b/layout/html/tests/Makefile.in index f9b08842e8de..eb9ad00f16df 100644 --- a/layout/html/tests/Makefile.in +++ b/layout/html/tests/Makefile.in @@ -50,6 +50,7 @@ REQUIRES = xpcom \ content \ layout \ gfx \ + thebes \ widget \ locale \ dom \ diff --git a/layout/inspector/src/Makefile.in b/layout/inspector/src/Makefile.in index 335b011883b1..0d0bf6138835 100644 --- a/layout/inspector/src/Makefile.in +++ b/layout/inspector/src/Makefile.in @@ -50,6 +50,7 @@ REQUIRES = xpcom \ string \ dom \ gfx \ + thebes \ layout \ content \ widget \ diff --git a/layout/printing/Makefile.in b/layout/printing/Makefile.in index 8d53c445954f..da2b6f5964b7 100644 --- a/layout/printing/Makefile.in +++ b/layout/printing/Makefile.in @@ -53,6 +53,7 @@ REQUIRES = xpcom \ dom \ widget \ gfx \ + thebes \ view \ necko \ webshell \ diff --git a/layout/printing/nsPrintEngine.cpp b/layout/printing/nsPrintEngine.cpp index cc90d9ddb4f8..20bd62c4179a 100644 --- a/layout/printing/nsPrintEngine.cpp +++ b/layout/printing/nsPrintEngine.cpp @@ -1918,7 +1918,7 @@ nsPrintEngine::ReflowPrintObject(nsPrintObject * aPO) NS_ASSERTION(aPO->mPresShell, "Presshell should still be here"); // Process the reflow event InitialReflow posted - aPO->mPresShell->FlushPendingNotifications(Flush_OnlyReflow); + aPO->mPresShell->FlushPendingNotifications(Flush_Layout); nsCOMPtr displayShell; aPO->mDocShell->GetPresShell(getter_AddRefs(displayShell)); diff --git a/layout/reftests/bugs/345267-1-ref.html b/layout/reftests/bugs/345267-1-ref.html new file mode 100644 index 000000000000..9bb47653ab66 --- /dev/null +++ b/layout/reftests/bugs/345267-1-ref.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/layout/reftests/bugs/345267-1a.html b/layout/reftests/bugs/345267-1a.html new file mode 100644 index 000000000000..feace49e6ba5 --- /dev/null +++ b/layout/reftests/bugs/345267-1a.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/layout/reftests/bugs/345267-1b.html b/layout/reftests/bugs/345267-1b.html new file mode 100644 index 000000000000..60726b6d8e9e --- /dev/null +++ b/layout/reftests/bugs/345267-1b.html @@ -0,0 +1,10 @@ + + + + + + + diff --git a/layout/reftests/bugs/345267-1c.html b/layout/reftests/bugs/345267-1c.html new file mode 100644 index 000000000000..1e78bec0dc3b --- /dev/null +++ b/layout/reftests/bugs/345267-1c.html @@ -0,0 +1,10 @@ + + + + + + + diff --git a/layout/reftests/bugs/345267-1d.html b/layout/reftests/bugs/345267-1d.html new file mode 100644 index 000000000000..d912035a530d --- /dev/null +++ b/layout/reftests/bugs/345267-1d.html @@ -0,0 +1,10 @@ + + + + + + + diff --git a/layout/reftests/bugs/385870-1-ref.html b/layout/reftests/bugs/385870-1-ref.html new file mode 100644 index 000000000000..fcebd34fb2d9 --- /dev/null +++ b/layout/reftests/bugs/385870-1-ref.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + diff --git a/layout/reftests/bugs/385870-1.html b/layout/reftests/bugs/385870-1.html new file mode 100644 index 000000000000..4d292e1c20a2 --- /dev/null +++ b/layout/reftests/bugs/385870-1.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + diff --git a/layout/reftests/bugs/385870-2-ref.html b/layout/reftests/bugs/385870-2-ref.html new file mode 100644 index 000000000000..eebef3805ea5 --- /dev/null +++ b/layout/reftests/bugs/385870-2-ref.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + diff --git a/layout/reftests/bugs/385870-2.html b/layout/reftests/bugs/385870-2.html new file mode 100644 index 000000000000..c641abb7968d --- /dev/null +++ b/layout/reftests/bugs/385870-2.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index b57a5b01f03e..d674c20833ac 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -146,6 +146,10 @@ fails-if(MOZ_WIDGET_TOOLKIT=="cocoa") == 336736-1.html 336736-1-ref.html # somet == 339289-1.html 339289-1-ref.html == 341043-1a.html 341043-1-ref.html != 341043-1b.html 341043-1-ref.html +== 345267-1a.html 345267-1-ref.html +== 345267-1b.html 345267-1-ref.html +== 345267-1c.html 345267-1-ref.html +== 345267-1d.html 345267-1-ref.html == 346774-1a.html 346774-1-ref.html == 346774-1b.html 346774-1-ref.html == 346774-1c.html 346774-1-ref.html @@ -341,6 +345,8 @@ random-if(MOZ_WIDGET_TOOLKIT=="cocoa") == 379316-2.html 379316-2-ref.html # bug == 384576-1.html 384576-1-ref.html == 384762-1.html about:blank == 384876-1.html 384876-1-ref.html +== 385870-1.html 385870-1-ref.html +== 385870-2.html 385870-2-ref.html == 386014-1a.html 386014-1-ref.html == 386014-1b.html 386014-1-ref.html == 386014-1c.html 386014-1-ref.html diff --git a/layout/reftests/svg/bugs/bug367368.xhtml b/layout/reftests/svg/bugs/bug367368.xhtml new file mode 100755 index 000000000000..b9bcd3241b8c --- /dev/null +++ b/layout/reftests/svg/bugs/bug367368.xhtml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/layout/reftests/svg/bugs/reftest.list b/layout/reftests/svg/bugs/reftest.list index 139597f9cb07..3859e1fcff78 100755 --- a/layout/reftests/svg/bugs/reftest.list +++ b/layout/reftests/svg/bugs/reftest.list @@ -1,2 +1,3 @@ +== bug367368.xhtml bug367368.xhtml # checking for crash, so just compared to self diff --git a/layout/reftests/svg/reftest.list b/layout/reftests/svg/reftest.list index 557a787ea05f..f7fe45c5675c 100755 --- a/layout/reftests/svg/reftest.list +++ b/layout/reftests/svg/reftest.list @@ -1,6 +1,6 @@ # bugs/ -# include bugs/reftest.list +include bugs/reftest.list fails-if(MOZ_WIDGET_TOOLKIT=="cocoa") == clipPath-basic-01.svg pass.svg # bug 379609 == foreignObject-01.svg pass.svg diff --git a/layout/style/Makefile.in b/layout/style/Makefile.in index 1ef5f0d9886e..264678fb557a 100644 --- a/layout/style/Makefile.in +++ b/layout/style/Makefile.in @@ -56,6 +56,7 @@ REQUIRES = xpcom \ dom \ content \ gfx \ + thebes \ widget \ caps \ locale \ diff --git a/layout/style/nsCSSScanner.cpp b/layout/style/nsCSSScanner.cpp index 1b624d661c67..d3bcf763239a 100644 --- a/layout/style/nsCSSScanner.cpp +++ b/layout/style/nsCSSScanner.cpp @@ -250,7 +250,7 @@ PR_STATIC_CALLBACK(int) CSSErrorsPrefChanged(const char *aPref, void *aClosure) } void nsCSSScanner::Init(nsIUnicharInputStream* aInput, - const PRUnichar * aBuffer, PRInt32 aCount, + const PRUnichar * aBuffer, PRUint32 aCount, nsIURI* aURI, PRUint32 aLineNumber) { NS_PRECONDITION(!mInputStream, "Should not have an existing input stream!"); @@ -288,7 +288,6 @@ void nsCSSScanner::Init(nsIUnicharInputStream* aInput, // Reset variables that we use to keep track of our progress through the input mOffset = 0; mPushbackCount = 0; - mLastRead = 0; #ifdef CSS_REPORT_PARSE_ERRORS mColNumber = 0; @@ -466,6 +465,24 @@ void nsCSSScanner::Close() #define TAB_STOP_WIDTH 8 #endif +PRBool nsCSSScanner::EnsureData(nsresult& aErrorCode) +{ + if (mOffset < mCount) + return PR_TRUE; + + if (mInputStream) { + mOffset = 0; + aErrorCode = mInputStream->Read(mBuffer, CSS_BUFFER_SIZE, &mCount); + if (NS_FAILED(aErrorCode) || mCount == 0) { + mCount = 0; + return PR_FALSE; + } + return PR_TRUE; + } + + return PR_FALSE; +} + // Returns -1 on error or eof PRInt32 nsCSSScanner::Read(nsresult& aErrorCode) { @@ -473,23 +490,21 @@ PRInt32 nsCSSScanner::Read(nsresult& aErrorCode) if (0 < mPushbackCount) { rv = PRInt32(mPushback[--mPushbackCount]); } else { - if (mCount < 0) { + if (mOffset == mCount && !EnsureData(aErrorCode)) { return -1; } - if (mOffset == mCount) { - mOffset = 0; - if (!mInputStream) { - mCount = 0; - return -1; - } - aErrorCode = mInputStream->Read(mBuffer, CSS_BUFFER_SIZE, (PRUint32*)&mCount); - if (NS_FAILED(aErrorCode) || mCount == 0) { - mCount = 0; - return -1; - } - } rv = PRInt32(mReadPointer[mOffset++]); - if (((rv == '\n') && (mLastRead != '\r')) || (rv == '\r')) { + // There are four types of newlines in CSS: "\r", "\n", "\r\n", and "\f". + // To simplify dealing with newlines, they are all normalized to "\n" here + if (rv == '\r') { + if (EnsureData(aErrorCode) && mReadPointer[mOffset] == '\n') { + mOffset++; + } + rv = '\n'; + } else if (rv == '\f') { + rv = '\n'; + } + if (rv == '\n') { // 0 is a magical line number meaning that we don't know (i.e., script) if (mLineNumber != 0) ++mLineNumber; @@ -506,7 +521,6 @@ PRInt32 nsCSSScanner::Read(nsresult& aErrorCode) } #endif } - mLastRead = rv; //printf("Read => %x\n", rv); return rv; } @@ -514,9 +528,7 @@ PRInt32 nsCSSScanner::Read(nsresult& aErrorCode) PRInt32 nsCSSScanner::Peek(nsresult& aErrorCode) { if (0 == mPushbackCount) { - PRInt32 savedLastRead = mLastRead; PRInt32 ch = Read(aErrorCode); - mLastRead = savedLastRead; if (ch < 0) { return -1; } @@ -527,13 +539,6 @@ PRInt32 nsCSSScanner::Peek(nsresult& aErrorCode) return PRInt32(mPushback[mPushbackCount - 1]); } -void nsCSSScanner::Unread() -{ - NS_PRECONDITION((mLastRead >= 0), "double pushback"); - Pushback(PRUnichar(mLastRead)); - mLastRead = -1; -} - void nsCSSScanner::Pushback(PRUnichar aChar) { if (mPushbackCount == mPushbackSize) { // grow buffer @@ -560,7 +565,7 @@ PRBool nsCSSScanner::LookAhead(nsresult& aErrorCode, PRUnichar aChar) if (ch == aChar) { return PR_TRUE; } - Unread(); + Pushback(ch); return PR_FALSE; } @@ -572,11 +577,11 @@ PRBool nsCSSScanner::EatWhiteSpace(nsresult& aErrorCode) if (ch < 0) { break; } - if ((ch == ' ') || (ch == '\n') || (ch == '\r') || (ch == '\t')) { + if ((ch == ' ') || (ch == '\n') || (ch == '\t')) { eaten = PR_TRUE; continue; } - Unread(); + Pushback(ch); break; } return eaten; @@ -589,16 +594,10 @@ PRBool nsCSSScanner::EatNewline(nsresult& aErrorCode) return PR_FALSE; } PRBool eaten = PR_FALSE; - if (ch == '\r') { - eaten = PR_TRUE; - ch = Peek(aErrorCode); - if (ch == '\n') { - (void) Read(aErrorCode); - } - } else if (ch == '\n') { + if (ch == '\n') { eaten = PR_TRUE; } else { - Unread(); + Pushback(ch); } return eaten; } @@ -820,7 +819,7 @@ PRBool nsCSSScanner::NextURL(nsresult& aErrorCode, nsCSSToken& aToken) // ")". This is an invalid url spec. ok = PR_FALSE; } else if (ch == ')') { - Unread(); + Pushback(ch); // All done break; } else { @@ -858,7 +857,7 @@ nsCSSScanner::ParseAndAppendEscape(nsresult& aErrorCode, nsString& aOutput) break; } if (ch >= 256 || (lexTable[ch] & (IS_HEX_DIGIT | IS_WHITESPACE)) == 0) { - Unread(); + Pushback(ch); break; } else if ((lexTable[ch] & IS_HEX_DIGIT) != 0) { if ((lexTable[ch] & IS_DIGIT) != 0) { @@ -872,10 +871,6 @@ nsCSSScanner::ParseAndAppendEscape(nsresult& aErrorCode, nsString& aOutput) } else { NS_ASSERTION((lexTable[ch] & IS_WHITESPACE) != 0, "bad control flow"); // single space ends escape - if (ch == '\r' && Peek(aErrorCode) == '\n') { - // if CR/LF, eat LF too - Read(aErrorCode); - } break; } } @@ -884,13 +879,6 @@ nsCSSScanner::ParseAndAppendEscape(nsresult& aErrorCode, nsString& aOutput) if ((0 <= ch) && (ch <= 255) && ((lexTable[ch] & IS_WHITESPACE) != 0)) { ch = Read(aErrorCode); - // special case: if trailing whitespace is CR/LF, eat both chars (not part of spec, but should be) - if (ch == '\r') { - ch = Peek(aErrorCode); - if (ch == '\n') { - ch = Read(aErrorCode); - } - } } } NS_ASSERTION(rv >= 0, "How did rv become negative?"); @@ -936,7 +924,7 @@ PRBool nsCSSScanner::GatherIdent(nsresult& aErrorCode, PRInt32 aChar, } else if ((aChar > 255) || ((gLexTable[aChar] & IS_IDENT) != 0)) { aIdent.Append(PRUnichar(aChar)); } else { - Unread(); + Pushback(aChar); break; } } @@ -963,7 +951,7 @@ PRBool nsCSSScanner::ParseRef(nsresult& aErrorCode, } // No ident chars after the '#'. Just unread |ch| and get out of here. - Unread(); + Pushback(ch); return PR_TRUE; } @@ -1039,7 +1027,7 @@ PRBool nsCSSScanner::ParseNumber(nsresult& aErrorCode, PRInt32 c, ident.SetLength(0); } else { // Put back character that stopped numeric scan - Unread(); + Pushback(c); if (!gotDot) { aToken.mInteger = ident.ToInteger(&ec); aToken.mIntegerValid = PR_TRUE; diff --git a/layout/style/nsCSSScanner.h b/layout/style/nsCSSScanner.h index 1efe2b79a414..1c2643d6a3aa 100644 --- a/layout/style/nsCSSScanner.h +++ b/layout/style/nsCSSScanner.h @@ -139,7 +139,7 @@ class nsCSSScanner { // when the line number is unknown. // Either aInput or (aBuffer and aCount) must be set. void Init(nsIUnicharInputStream* aInput, - const PRUnichar *aBuffer, PRInt32 aCount, + const PRUnichar *aBuffer, PRUint32 aCount, nsIURI* aURI, PRUint32 aLineNumber); void Close(); @@ -198,9 +198,9 @@ class nsCSSScanner { } protected: + PRBool EnsureData(nsresult& aErrorCode); PRInt32 Read(nsresult& aErrorCode); PRInt32 Peek(nsresult& aErrorCode); - void Unread(); void Pushback(PRUnichar aChar); PRBool LookAhead(nsresult& aErrorCode, PRUnichar aChar); PRBool EatWhiteSpace(nsresult& aErrorCode); @@ -226,12 +226,11 @@ protected: PRUnichar mBuffer[CSS_BUFFER_SIZE]; const PRUnichar *mReadPointer; - PRInt32 mOffset; - PRInt32 mCount; + PRUint32 mOffset; + PRUint32 mCount; PRUnichar* mPushback; PRInt32 mPushbackCount; PRInt32 mPushbackSize; - PRInt32 mLastRead; PRUnichar mLocalPushback[4]; PRUint32 mLineNumber; diff --git a/layout/style/nsInspectorCSSUtils.cpp b/layout/style/nsInspectorCSSUtils.cpp index 3da04911907f..0e38d7f8d38f 100644 --- a/layout/style/nsInspectorCSSUtils.cpp +++ b/layout/style/nsInspectorCSSUtils.cpp @@ -122,7 +122,7 @@ nsInspectorCSSUtils::GetStyleContextForContent(nsIContent* aContent, nsIPresShell* aPresShell) { if (!aPseudo) { - aPresShell->FlushPendingNotifications(Flush_StyleReresolves); + aPresShell->FlushPendingNotifications(Flush_Style); nsIFrame* frame = aPresShell->GetPrimaryFrameFor(aContent); if (frame) { nsStyleContext* result = GetStyleContextForFrame(frame); diff --git a/layout/svg/base/src/nsSVGOuterSVGFrame.cpp b/layout/svg/base/src/nsSVGOuterSVGFrame.cpp index 153974238e84..1d4c23614e17 100644 --- a/layout/svg/base/src/nsSVGOuterSVGFrame.cpp +++ b/layout/svg/base/src/nsSVGOuterSVGFrame.cpp @@ -148,7 +148,6 @@ NS_NewSVGOuterSVGFrame(nsIPresShell* aPresShell, nsIContent* aContent, nsStyleCo nsSVGOuterSVGFrame::nsSVGOuterSVGFrame(nsStyleContext* aContext) : nsSVGOuterSVGFrameBase(aContext), mRedrawSuspendCount(0), - mNeedsReflow(PR_FALSE), mViewportInitialized(PR_FALSE) { } @@ -510,16 +509,12 @@ nsSVGOuterSVGFrame::UnsuspendRedraw() #ifdef DEBUG // printf("unsuspend redraw (count=%d)\n", mRedrawSuspendCount); #endif + + NS_ASSERTION(mRedrawSuspendCount >=0, "unbalanced suspend count!"); + if (--mRedrawSuspendCount > 0) return NS_OK; - - NS_ASSERTION(mRedrawSuspendCount >=0, "unbalanced suspend count!"); - - // If we need to reflow, do so before we update any of our - // children. Reflows are likely to affect the display of children: - if (mNeedsReflow) - InitiateReflow(); - + for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { nsISVGChildFrame* SVGFrame=nsnull; @@ -601,20 +596,6 @@ nsSVGOuterSVGFrame::GetCanvasTM() //---------------------------------------------------------------------- // Implementation helpers -void nsSVGOuterSVGFrame::InitiateReflow() -{ - mNeedsReflow = PR_FALSE; - - nsIPresShell* presShell = PresContext()->PresShell(); - presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange, - NS_FRAME_IS_DIRTY); - // XXXbz why is this synchronously flushing reflows, exactly? If it - // needs to, why is it not using the presshell's reflow batching - // instead of hacking its own? - presShell->FlushPendingNotifications(Flush_OnlyReflow); -} - - void nsSVGOuterSVGFrame::CalculateAvailableSpace(nsRect *maxRect, nsRect *preferredRect, diff --git a/layout/svg/base/src/nsSVGOuterSVGFrame.h b/layout/svg/base/src/nsSVGOuterSVGFrame.h index a1a88e6cda00..a30a0e6dabd9 100644 --- a/layout/svg/base/src/nsSVGOuterSVGFrame.h +++ b/layout/svg/base/src/nsSVGOuterSVGFrame.h @@ -125,8 +125,6 @@ public: virtual already_AddRefed GetCanvasTM(); protected: - // implementation helpers: - void InitiateReflow(); void CalculateAvailableSpace(nsRect *maxRect, nsRect *preferredRect, nsPresContext* aPresContext, @@ -140,7 +138,6 @@ protected: nsCOMPtr mCurrentTranslate; nsCOMPtr mCurrentScale; - PRPackedBool mNeedsReflow; PRPackedBool mViewportInitialized; }; diff --git a/layout/tools/reftest/reftest.js b/layout/tools/reftest/reftest.js index b6ed608e1e0c..41808be1119c 100644 --- a/layout/tools/reftest/reftest.js +++ b/layout/tools/reftest/reftest.js @@ -51,7 +51,7 @@ const NS_SCRIPTSECURITYMANAGER_CONTRACTID = const NS_REFTESTHELPER_CONTRACTID = "@mozilla.org/reftest-helper;1"; -const LOAD_FAILURE_TIMEOUT = 10000; // ms +const LOAD_FAILURE_TIMEOUT = 30000; // ms var gBrowser; var gCanvas1, gCanvas2; diff --git a/layout/xul/base/src/grid/nsGridLayout2.cpp b/layout/xul/base/src/grid/nsGridLayout2.cpp index 728251378077..d8ee6898f5a0 100644 --- a/layout/xul/base/src/grid/nsGridLayout2.cpp +++ b/layout/xul/base/src/grid/nsGridLayout2.cpp @@ -44,6 +44,7 @@ #include "nsGridLayout2.h" #include "nsGridRowGroupLayout.h" +#include "nsGridRow.h" #include "nsBox.h" #include "nsIScrollableFrame.h" #include "nsSprocketLayout.h" @@ -247,6 +248,16 @@ nsGridLayout2::GetMaxSize(nsIBox* aBox, nsBoxLayoutState& aState, nsSize& aSize) return rv; } +PRInt32 +nsGridLayout2::BuildRows(nsIBox* aBox, nsGridRow* aRows) +{ + if (aBox) { + aRows[0].Init(aBox, PR_TRUE); + return 1; + } + return 0; +} + nsMargin nsGridLayout2::GetTotalMargin(nsIBox* aBox, PRBool aIsHorizontal) { diff --git a/layout/xul/base/src/grid/nsGridLayout2.h b/layout/xul/base/src/grid/nsGridLayout2.h index bbaec5aa911f..88fafca04995 100644 --- a/layout/xul/base/src/grid/nsGridLayout2.h +++ b/layout/xul/base/src/grid/nsGridLayout2.h @@ -74,9 +74,9 @@ public: NS_IMETHOD GetMinSize(nsIBox* aBox, nsBoxLayoutState& aBoxLayoutState, nsSize& aSize); NS_IMETHOD GetPrefSize(nsIBox* aBox, nsBoxLayoutState& aBoxLayoutState, nsSize& aSize); NS_IMETHOD GetMaxSize(nsIBox* aBox, nsBoxLayoutState& aBoxLayoutState, nsSize& aSize); - virtual void CountRowsColumns(nsIBox* aBox, PRInt32& aRowCount, PRInt32& aComputedColumnCount) { NS_NOTREACHED("Should not be called"); } + virtual void CountRowsColumns(nsIBox* aBox, PRInt32& aRowCount, PRInt32& aComputedColumnCount) { aRowCount++; } virtual void DirtyRows(nsIBox* aBox, nsBoxLayoutState& aState) { NS_NOTREACHED("Should not be called"); } - virtual PRInt32 BuildRows(nsIBox* aBox, nsGridRow* aRows) { NS_NOTREACHED("Should not be called"); return 0; } + virtual PRInt32 BuildRows(nsIBox* aBox, nsGridRow* aRows); virtual nsMargin GetTotalMargin(nsIBox* aBox, PRBool aIsHorizontal); virtual Type GetType() { return eGrid; } NS_IMETHOD ChildrenInserted(nsIBox* aBox, nsBoxLayoutState& aState, diff --git a/layout/xul/base/src/nsListBoxBodyFrame.cpp b/layout/xul/base/src/nsListBoxBodyFrame.cpp index 5ba269f65c4f..5632c9d92fab 100644 --- a/layout/xul/base/src/nsListBoxBodyFrame.cpp +++ b/layout/xul/base/src/nsListBoxBodyFrame.cpp @@ -263,6 +263,11 @@ nsListBoxBodyFrame::Destroy() if (mReflowCallbackPosted) PresContext()->PresShell()->CancelReflowCallback(this); + // Revoke any pending position changed events + for (PRUint32 i = 0; i < mPendingPositionChangeEvents.Length(); ++i) { + mPendingPositionChangeEvents[i]->Revoke(); + } + // Make sure we tell our listbox's box object we're being destroyed. for (nsIFrame *a = mParent; a; a = a->GetParent()) { nsIContent *content = a->GetContent(); @@ -421,9 +426,6 @@ nsListBoxBodyFrame::PositionChanged(nsISupports* aScrollbar, PRInt32 aOldIndex, smoother->Stop(); - // Don't flush anything but reflows lest it destroy us - mContent->GetDocument()->FlushPendingNotifications(Flush_OnlyReflow); - smoother->mDelta = newTwipIndex > oldTwipIndex ? rowDelta : -rowDelta; smoother->Start(); @@ -568,7 +570,9 @@ nsListBoxBodyFrame::EnsureIndexIsVisible(PRInt32 aRowIndex) mCurrentIndex += delta; } - InternalPositionChanged(up, delta); + // Safe to not go off an event here, since this is coming from the + // box object. + DoInternalPositionChangedSync(up, delta); return NS_OK; } @@ -595,6 +599,7 @@ nsListBoxBodyFrame::ScrollByLines(PRInt32 aNumLines) // we have to do a sync update for mac because if we scroll too quickly // w/out going back to the main event loop we can easily scroll the wrong // bits and it looks like garbage (bug 63465). + // XXXbz is this seriously still needed? // I'd use Composite here, but it doesn't always work. // vm->Composite(); @@ -844,17 +849,19 @@ nsListBoxBodyFrame::ScrollToIndex(PRInt32 aRowIndex) return NS_OK; mCurrentIndex = newIndex; - InternalPositionChanged(up, delta); + + // Since we're going to flush anyway, we need to not do this off an event + DoInternalPositionChangedSync(up, delta); // This change has to happen immediately. // Flush any pending reflow commands. - // Don't flush anything but reflows lest it destroy us - mContent->GetDocument()->FlushPendingNotifications(Flush_OnlyReflow); + // XXXbz why, exactly? + mContent->GetDocument()->FlushPendingNotifications(Flush_Layout); return NS_OK; } -NS_IMETHODIMP +nsresult nsListBoxBodyFrame::InternalPositionChangedCallback() { nsListScrollSmoother* smoother = GetSmoother(); @@ -867,12 +874,49 @@ nsListBoxBodyFrame::InternalPositionChangedCallback() if (mCurrentIndex < 0) mCurrentIndex = 0; - return InternalPositionChanged(smoother->mDelta < 0, smoother->mDelta < 0 ? -smoother->mDelta : smoother->mDelta); + return DoInternalPositionChangedSync(smoother->mDelta < 0, + smoother->mDelta < 0 ? + -smoother->mDelta : smoother->mDelta); } -NS_IMETHODIMP +nsresult nsListBoxBodyFrame::InternalPositionChanged(PRBool aUp, PRInt32 aDelta) -{ +{ + nsRefPtr ev = + new nsPositionChangedEvent(this, aUp, aDelta); + nsresult rv = NS_DispatchToCurrentThread(ev); + if (NS_SUCCEEDED(rv)) { + if (!mPendingPositionChangeEvents.AppendElement(ev)) { + rv = NS_ERROR_OUT_OF_MEMORY; + ev->Revoke(); + } + } + return rv; +} + +nsresult +nsListBoxBodyFrame::DoInternalPositionChangedSync(PRBool aUp, PRInt32 aDelta) +{ + nsWeakFrame weak(this); + + // Process all the pending position changes first + nsTArray< nsRefPtr > temp; + temp.SwapElements(mPendingPositionChangeEvents); + for (PRUint32 i = 0; i < temp.Length(); ++i) { + temp[i]->Run(); + temp[i]->Revoke(); + } + + if (!weak.IsAlive()) { + return NS_OK; + } + + return DoInternalPositionChanged(aUp, aDelta); +} + +nsresult +nsListBoxBodyFrame::DoInternalPositionChanged(PRBool aUp, PRInt32 aDelta) +{ if (aDelta == 0) return NS_OK; @@ -882,7 +926,11 @@ nsListBoxBodyFrame::InternalPositionChanged(PRBool aUp, PRInt32 aDelta) // begin timing how long it takes to scroll a row PRTime start = PR_Now(); - mContent->GetDocument()->FlushPendingNotifications(Flush_OnlyReflow); + nsWeakFrame weakThis(this); + mContent->GetDocument()->FlushPendingNotifications(Flush_Layout); + if (!weakThis.IsAlive()) { + return NS_OK; + } PRInt32 visibleRows = 0; if (mRowHeight) @@ -922,7 +970,11 @@ nsListBoxBodyFrame::InternalPositionChanged(PRBool aUp, PRInt32 aDelta) FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN); // Flush calls CreateRows // XXXbz there has to be a better way to do this than flushing! - presContext->PresShell()->FlushPendingNotifications(Flush_OnlyReflow); + presContext->PresShell()->FlushPendingNotifications(Flush_Layout); + if (!weakThis.IsAlive()) { + return NS_OK; + } + mScrolling = PR_FALSE; VerticalScroll(mYPosition); diff --git a/layout/xul/base/src/nsListBoxBodyFrame.h b/layout/xul/base/src/nsListBoxBodyFrame.h index 6e6b41553102..fcb8a109e2bc 100644 --- a/layout/xul/base/src/nsListBoxBodyFrame.h +++ b/layout/xul/base/src/nsListBoxBodyFrame.h @@ -48,6 +48,7 @@ #include "nsIReflowCallback.h" #include "nsPresContext.h" #include "nsBoxLayoutState.h" +#include "nsThreadUtils.h" class nsListScrollSmoother; nsIFrame* NS_NewListBoxBodyFrame(nsIPresShell* aPresShell, @@ -105,8 +106,13 @@ public: nscoord ComputeIntrinsicWidth(nsBoxLayoutState& aBoxLayoutState); // scrolling - NS_IMETHOD InternalPositionChangedCallback(); - NS_IMETHOD InternalPositionChanged(PRBool aUp, PRInt32 aDelta); + nsresult InternalPositionChangedCallback(); + nsresult InternalPositionChanged(PRBool aUp, PRInt32 aDelta); + // Process pending position changed events, then do the position change. + // This can wipe out the frametree. + nsresult DoInternalPositionChangedSync(PRBool aUp, PRInt32 aDelta); + // Actually do the internal position change. This can wipe out the frametree + nsresult DoInternalPositionChanged(PRBool aUp, PRInt32 aDelta); nsListScrollSmoother* GetSmoother(); void VerticalScroll(PRInt32 aDelta); @@ -132,6 +138,37 @@ public: void PostReflowCallback(); protected: + class nsPositionChangedEvent; + friend class nsPositionChangedEvent; + + class nsPositionChangedEvent : public nsRunnable + { + public: + nsPositionChangedEvent(nsListBoxBodyFrame* aFrame, + PRBool aUp, PRInt32 aDelta) : + mFrame(aFrame), mUp(aUp), mDelta(aDelta) + {} + + NS_IMETHOD Run() + { + if (!mFrame) { + return NS_OK; + } + + mFrame->mPendingPositionChangeEvents.RemoveElement(this); + + return mFrame->DoInternalPositionChanged(mUp, mDelta); + } + + void Revoke() { + mFrame = nsnull; + } + + nsListBoxBodyFrame* mFrame; + PRBool mUp; + PRInt32 mDelta; + }; + void ComputeTotalRowCount(); void RemoveChildFrame(nsBoxLayoutState &aState, nsIFrame *aChild); @@ -157,6 +194,8 @@ protected: nsListScrollSmoother* mScrollSmoother; PRInt32 mTimePerRow; + nsTArray< nsRefPtr > mPendingPositionChangeEvents; + PRPackedBool mReflowCallbackPosted; }; diff --git a/layout/xul/base/src/nsMenuBarFrame.cpp b/layout/xul/base/src/nsMenuBarFrame.cpp index 15df2a0a7d62..e57d316839bb 100644 --- a/layout/xul/base/src/nsMenuBarFrame.cpp +++ b/layout/xul/base/src/nsMenuBarFrame.cpp @@ -255,6 +255,8 @@ nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent) { PRUint32 charCode; aKeyEvent->GetCharCode(&charCode); + if (!charCode) // no character was pressed so just return + return nsnull; // Enumerate over our list of frames. nsIFrame* immediateParent = nsnull; diff --git a/layout/xul/base/src/nsPopupSetFrame.cpp b/layout/xul/base/src/nsPopupSetFrame.cpp index d38c45502586..f32bcaf30861 100644 --- a/layout/xul/base/src/nsPopupSetFrame.cpp +++ b/layout/xul/base/src/nsPopupSetFrame.cpp @@ -213,6 +213,13 @@ nsPopupSetFrame::DoLayout(nsBoxLayoutState& aState) // layout the child popupChild->Layout(aState); + // if the width or height changed, readjust the popup position. This is a + // special case for tooltips where the preferred height doesn't include the + // real height for its inline element, but does once it is laid out. + // This is bug 228673 which doesn't have a simple fix. + if (popupChild->GetRect().width > bounds.width || + popupChild->GetRect().height > bounds.height) + popupChild->SetPopupPosition(nsnull); popupChild->AdjustView(); } diff --git a/layout/xul/base/src/nsTextBoxFrame.cpp b/layout/xul/base/src/nsTextBoxFrame.cpp index ef037080b7f0..eb35e04e0942 100644 --- a/layout/xul/base/src/nsTextBoxFrame.cpp +++ b/layout/xul/base/src/nsTextBoxFrame.cpp @@ -452,14 +452,14 @@ nsTextBoxFrame::PaintTitle(nsIRenderingContext& aRenderingContext, nsRefPtr ctx = (gfxContext*) aRenderingContext.GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT); - gfxFloat a2p = 1.0 / presContext->AppUnitsPerDevPixel(); - gfxPoint pt(textRect.x * a2p, textRect.y * a2p); - gfxFloat width = textRect.width * a2p; - gfxFloat baselinePixel = baseline * a2p; + gfxPoint pt(presContext->AppUnitsToGfxUnits(textRect.x), + presContext->AppUnitsToGfxUnits(textRect.y)); + gfxFloat width = presContext->AppUnitsToGfxUnits(textRect.width); + gfxFloat baselinePixel = presContext->AppUnitsToGfxUnits(baseline); if (decorations & (NS_FONT_DECORATION_OVERLINE | NS_FONT_DECORATION_UNDERLINE)) { fontMet->GetUnderline(offset, size); - gfxFloat offsetPixel = offset * a2p; - gfxFloat sizePixel = size * a2p; + gfxFloat offsetPixel = presContext->AppUnitsToGfxUnits(offset); + gfxFloat sizePixel = presContext->AppUnitsToGfxUnits(size); if (decorations & NS_FONT_DECORATION_OVERLINE) { nsCSSRendering::PaintDecorationLine(ctx, overColor, pt, gfxSize(width, sizePixel), @@ -481,8 +481,8 @@ nsTextBoxFrame::PaintTitle(nsIRenderingContext& aRenderingContext, } if (decorations & NS_FONT_DECORATION_LINE_THROUGH) { fontMet->GetStrikeout(offset, size); - gfxFloat offsetPixel = offset * a2p; - gfxFloat sizePixel = size * a2p; + gfxFloat offsetPixel = presContext->AppUnitsToGfxUnits(offset); + gfxFloat sizePixel = presContext->AppUnitsToGfxUnits(size); nsCSSRendering::PaintDecorationLine(ctx, underColor, pt, gfxSize(width, sizePixel), baselinePixel, offsetPixel, diff --git a/layout/xul/base/src/nsXULPopupManager.cpp b/layout/xul/base/src/nsXULPopupManager.cpp index c4e5e6187928..079f100db4b2 100644 --- a/layout/xul/base/src/nsXULPopupManager.cpp +++ b/layout/xul/base/src/nsXULPopupManager.cpp @@ -517,29 +517,44 @@ nsXULPopupManager::HidePopup(nsIContent* aPopup, PRBool deselectMenu = PR_FALSE; nsCOMPtr popupToHide, nextPopup, lastPopup; if (foundMenu) { - // at this point, item will be set to the found item in the list. If item - // is the topmost menu, the one to remove, then there are no other - // popups to hide. If item is not the topmost menu, then there are + // at this point, foundMenu will be set to the found item in the list. If + // foundMenu is the topmost menu, the one to remove, then there are no other + // popups to hide. If foundMenu is not the topmost menu, then there may be // open submenus below it. In this case, we need to make sure that those - // submenus are closed up first. To do this, we start at mCurrentMenu and - // close that popup. In synchronous mode, the FirePopupHidingEvent method - // will be called which in turn calls HidePopupCallback to close up the - // next popup in the chain. These two methods will be called in sequence - // recursively to close up all the necessary popups. In asynchronous mode, - // a similar process occurs except that the FirePopupHidingEvent method is - // called asynchrounsly. In either case, nextPopup is set to the content - // node of the next popup to close, and lastPopup is set to the last popup - // in the chain to close, which will be aPopup. + // submenus are closed up first. To do this, we scan up the menu list to + // find the topmost popup with only menus between it and foundMenu and + // close that menu first. In synchronous mode, the FirePopupHidingEvent + // method will be called which in turn calls HidePopupCallback to close up + // the next popup in the chain. These two methods will be called in + // sequence recursively to close up all the necessary popups. In + // asynchronous mode, a similar process occurs except that the + // FirePopupHidingEvent method is called asynchrounsly. In either case, + // nextPopup is set to the content node of the next popup to close, and + // lastPopup is set to the last popup in the chain to close, which will be + // aPopup, or null to close up all menus. + + nsMenuChainItem* topMenu = foundMenu; + // Use IsMenu to ensure that foundMenu is a menu and scan down the child + // list until a non-menu is found. If foundMenu isn't a menu at all, don't + // scan and just close up this menu. + if (foundMenu->IsMenu()) { + item = topMenu->GetChild(); + while (item && item->IsMenu()) { + topMenu = item; + item = item->GetChild(); + } + } + deselectMenu = aDeselectMenu; - popupToHide = mCurrentMenu->Content(); - popupFrame = mCurrentMenu->Frame(); + popupToHide = topMenu->Content(); + popupFrame = topMenu->Frame(); type = popupFrame->PopupType(); - nsMenuChainItem* parent = mCurrentMenu->GetParent(); + nsMenuChainItem* parent = topMenu->GetParent(); // close up another popup if there is one, and we are either hiding the // entire chain or the item to hide isn't the topmost popup. - if (parent && (aHideChain || mCurrentMenu != item)) + if (parent && (aHideChain || topMenu != foundMenu)) nextPopup = parent->Content(); lastPopup = aHideChain ? nsnull : aPopup; @@ -1604,8 +1619,10 @@ nsXULPopupManager::IsValidMenuItem(nsPresContext* aPresContext, nsresult nsXULPopupManager::KeyUp(nsIDOMEvent* aKeyEvent) { - aKeyEvent->StopPropagation(); - aKeyEvent->PreventDefault(); + if (mCurrentMenu) { + aKeyEvent->StopPropagation(); + aKeyEvent->PreventDefault(); + } return NS_OK; // I am consuming event } @@ -1613,6 +1630,10 @@ nsXULPopupManager::KeyUp(nsIDOMEvent* aKeyEvent) nsresult nsXULPopupManager::KeyDown(nsIDOMEvent* aKeyEvent) { + // don't do anything if a menu isn't open + if (!mCurrentMenu) + return NS_OK; + PRInt32 menuAccessKey = -1; // If the key just pressed is the access key (usually Alt), @@ -1726,8 +1747,12 @@ nsXULPopupManager::KeyPress(nsIDOMEvent* aKeyEvent) HandleShortcutNavigation(keyEvent, nsnull); } - aKeyEvent->StopPropagation(); - aKeyEvent->PreventDefault(); + if (mCurrentMenu) { + // if a menu is open, it consumes the key event + aKeyEvent->StopPropagation(); + aKeyEvent->PreventDefault(); + } + return NS_OK; // I am consuming event } @@ -1784,8 +1809,21 @@ nsXULMenuCommandEvent::Run() // associated view manager on exit from this function. // See bug 54233. // XXXndeakin is this still needed? + + nsCOMPtr popup; nsMenuFrame* menuFrame = pm->GetMenuFrameForContent(mMenu); if (menuFrame) { + // Find the popup that the menu is inside. Below, this popup will + // need to be hidden. + nsIFrame* popupFrame = menuFrame->GetParent(); + while (popupFrame) { + if (popupFrame->GetType() == nsGkAtoms::menuPopupFrame) { + popup = popupFrame->GetContent(); + break; + } + popupFrame = popupFrame->GetParent(); + } + nsPresContext* presContext = menuFrame->PresContext(); nsCOMPtr kungFuDeathGrip = presContext->GetViewManager(); nsCOMPtr shell = presContext->PresShell(); @@ -1804,7 +1842,8 @@ nsXULMenuCommandEvent::Run() shell->HandleDOMEventWithTarget(mMenu, &commandEvent, &status); } - pm->Rollup(); + if (popup) + pm->HidePopup(popup, PR_TRUE, PR_TRUE, PR_TRUE); return NS_OK; } diff --git a/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp b/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp index 50cbcfb3a90f..1e702f8a0092 100644 --- a/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp +++ b/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp @@ -149,6 +149,7 @@ nsTreeBodyFrame::nsTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aCont mTopRowIndex(0), mHorzPosition(0), mHorzWidth(0), + mAdjustWidth(0), mRowHeight(0), mIndentation(0), mStringWidth(-1), @@ -159,6 +160,7 @@ nsTreeBodyFrame::nsTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aCont mReflowCallbackPosted(PR_FALSE), mUpdateBatchNest(0), mRowCount(0), + mMouseOverRow(-1), mSlots(nsnull) { mColumns = new nsTreeColumns(nsnull); @@ -773,6 +775,7 @@ FindScrollParts(nsIFrame* aCurrFrame, nsTreeBodyFrame::ScrollParts* aResult) nsIScrollableFrame* f; CallQueryInterface(aCurrFrame, &f); if (f) { + aResult->mColumnsFrame = aCurrFrame; aResult->mColumnsScrollableView = f->GetScrollableView(); } } @@ -805,7 +808,7 @@ FindScrollParts(nsIFrame* aCurrFrame, nsTreeBodyFrame::ScrollParts* aResult) nsTreeBodyFrame::ScrollParts nsTreeBodyFrame::GetScrollParts() { nsPresContext* presContext = PresContext(); - ScrollParts result = { nsnull, nsnull, nsnull, nsnull, nsnull }; + ScrollParts result = { nsnull, nsnull, nsnull, nsnull, nsnull, nsnull }; nsIContent* baseElement = GetBaseElement(); nsIFrame* treeFrame = baseElement ? presContext->PresShell()->GetPrimaryFrameFor(baseElement) : nsnull; @@ -865,8 +868,8 @@ nsTreeBodyFrame::CheckOverflow(const ScrollParts& aParts) verticalOverflowChanged = PR_TRUE; } - if (aParts.mColumnsScrollableView) { - nsRect bounds = aParts.mColumnsScrollableView->View()->GetBounds(); + if (aParts.mColumnsFrame) { + nsRect bounds = aParts.mColumnsFrame->GetRect(); if (bounds.width != 0) { /* Ignore overflows that are less than half a pixel. Yes these happen all over the place when flex boxes are compressed real small. @@ -929,9 +932,9 @@ nsTreeBodyFrame::InvalidateScrollbars(const ScrollParts& aParts) ENSURE_TRUE(weakFrame.IsAlive()); } - if (aParts.mHScrollbar && aParts.mColumnsScrollableView) { + if (aParts.mHScrollbar && aParts.mColumnsFrame) { // And now Horizontal scrollbar - nsRect bounds = aParts.mColumnsScrollableView->View()->GetBounds(); + nsRect bounds = aParts.mColumnsFrame->GetRect(); nsAutoString maxposStr; maxposStr.AppendInt(mHorzWidth > bounds.width ? mHorzWidth - bounds.width : 0); @@ -1896,9 +1899,12 @@ nsTreeBodyFrame::PrefillPropertyArray(PRInt32 aRowIndex, nsTreeColumn* aCol) mScratchArray->AppendElement(nsGkAtoms::dragSession); if (aRowIndex != -1) { + if (aRowIndex == mMouseOverRow) + mScratchArray->AppendElement(nsGkAtoms::hover); + nsCOMPtr selection; mView->GetSelection(getter_AddRefs(selection)); - + if (selection) { // selected PRBool isSelected; @@ -2438,12 +2444,15 @@ nsTreeBodyFrame::CalcHorzWidth(const ScrollParts& aParts) } // If no horz scrolling periphery is present, then just - // return the width of the main box + // return the width of the columns if (width == 0) { - CalcInnerBox(); - width = mInnerBox.width; + width = aParts.mColumnsFrame->GetRect().width; } + // Compute the adjustment to the last column. This varies depending on the + // visibility of the columnpicker and the scrollbar. + mAdjustWidth = mRect.width - aParts.mColumnsFrame->GetRect().width; + return width; } @@ -2478,7 +2487,27 @@ nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext, nsGUIEvent* aEvent, nsEventStatus* aEventStatus) { - if (aEvent->message == NS_DRAGDROP_ENTER) { + if (aEvent->message == NS_MOUSE_ENTER_SYNTH || aEvent->message == NS_MOUSE_MOVE) { + nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this); + PRInt32 xTwips = pt.x - mInnerBox.x; + PRInt32 yTwips = pt.y - mInnerBox.y; + PRInt32 newrow = GetRowAt(xTwips, yTwips); + if (mMouseOverRow != newrow) { + // redraw the old and the new row + if (mMouseOverRow != -1) + InvalidateRow(mMouseOverRow); + mMouseOverRow = newrow; + if (mMouseOverRow != -1) + InvalidateRow(mMouseOverRow); + } + } + else if (aEvent->message == NS_MOUSE_EXIT_SYNTH) { + if (mMouseOverRow != -1) { + InvalidateRow(mMouseOverRow); + mMouseOverRow = -1; + } + } + else if (aEvent->message == NS_DRAGDROP_ENTER) { if (!mSlots) mSlots = new Slots(); @@ -2717,11 +2746,11 @@ nsTreeBodyFrame::PaintTreeBody(nsIRenderingContext& aRenderingContext, PresContext()->PresShell()-> FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); } - #ifdef DEBUG +#ifdef DEBUG PRInt32 rowCount = mRowCount; - mView->GetRowCount(&mRowCount); - NS_ASSERTION(mRowCount == rowCount, "row count changed unexpectedly"); - #endif + mView->GetRowCount(&rowCount); + NS_WARN_IF_FALSE(mRowCount == rowCount, "row count changed unexpectedly"); +#endif // Loop through our columns and paint them (e.g., for sorting). This is only // relevant when painting backgrounds, since columns contain no content. Content @@ -3768,6 +3797,9 @@ NS_IMETHODIMP nsTreeBodyFrame::EnsureCellIsVisible(PRInt32 aRow, nsITreeColumn* rv = col->GetWidthInTwips(this, &columnWidth); if(NS_FAILED(rv)) return rv; + if (!col->GetNext()) + columnWidth -= mAdjustWidth; // this is one case we don't want to adjust + // If the start of the column is before the // start of the horizontal view, then scroll if (columnPos < mHorzPosition) @@ -3950,7 +3982,7 @@ nsTreeBodyFrame::ScrollHorzInternal(const ScrollParts& aParts, PRInt32 aPosition if (aPosition < 0 || aPosition > mHorzWidth) return NS_OK; - nsRect bounds = aParts.mColumnsScrollableView->View()->GetBounds(); + nsRect bounds = aParts.mColumnsFrame->GetRect(); if (aPosition > (mHorzWidth - bounds.width)) aPosition = mHorzWidth - bounds.width; diff --git a/layout/xul/base/src/tree/src/nsTreeBodyFrame.h b/layout/xul/base/src/tree/src/nsTreeBodyFrame.h index fdbcbf81016b..16f252a3279a 100644 --- a/layout/xul/base/src/tree/src/nsTreeBodyFrame.h +++ b/layout/xul/base/src/tree/src/nsTreeBodyFrame.h @@ -123,12 +123,14 @@ public: NS_IMETHOD DidSetStyleContext(); friend nsIFrame* NS_NewTreeBodyFrame(nsIPresShell* aPresShell); + friend class nsTreeColumn; struct ScrollParts { nsIScrollbarFrame* mVScrollbar; nsIContent* mVScrollbarContent; nsIScrollbarFrame* mHScrollbar; nsIContent* mHScrollbarContent; + nsIFrame* mColumnsFrame; nsIScrollableView* mColumnsScrollableView; }; @@ -444,6 +446,9 @@ protected: // Data Members // Our desired horizontal width (the width for which we actually have tree // columns). nscoord mHorzWidth; + // The amount by which to adjust the width of the last cell. + // This depends on whether or not the columnpicker and scrollbars are present. + nscoord mAdjustWidth; // Cached heights and indent info. nsRect mInnerBox; @@ -470,6 +475,9 @@ protected: // Data Members // Cached row count. PRInt32 mRowCount; + // The row the mouse is hovering over. + PRInt32 mMouseOverRow; + class Slots { public: Slots() diff --git a/layout/xul/base/src/tree/src/nsTreeColumns.cpp b/layout/xul/base/src/tree/src/nsTreeColumns.cpp index 04ef3fd47566..fd9a9e790661 100644 --- a/layout/xul/base/src/tree/src/nsTreeColumns.cpp +++ b/layout/xul/base/src/tree/src/nsTreeColumns.cpp @@ -48,8 +48,7 @@ #include "nsIDOMClassInfo.h" #include "nsINodeInfo.h" #include "nsContentUtils.h" - -static NS_DEFINE_CID(kTreeColumnImplCID, NS_TREECOLUMN_IMPL_CID); +#include "nsTreeBodyFrame.h" // Column class that caches all the info about our column. nsTreeColumn::nsTreeColumn(nsTreeColumns* aColumns, nsIContent* aContent) @@ -88,7 +87,7 @@ NS_IMPL_ADDREF(nsTreeColumn) NS_IMPL_RELEASE(nsTreeColumn) nsIFrame* -nsTreeColumn::GetFrame(nsIFrame* aBodyFrame) +nsTreeColumn::GetFrame(nsTreeBodyFrame* aBodyFrame) { NS_PRECONDITION(aBodyFrame, "null frame?"); @@ -114,7 +113,7 @@ nsTreeColumn::GetFrame() } nsresult -nsTreeColumn::GetRect(nsIFrame* aBodyFrame, nscoord aY, nscoord aHeight, nsRect* aResult) +nsTreeColumn::GetRect(nsTreeBodyFrame* aBodyFrame, nscoord aY, nscoord aHeight, nsRect* aResult) { nsIFrame* frame = GetFrame(aBodyFrame); if (!frame) { @@ -125,11 +124,13 @@ nsTreeColumn::GetRect(nsIFrame* aBodyFrame, nscoord aY, nscoord aHeight, nsRect* *aResult = frame->GetRect(); aResult->y = aY; aResult->height = aHeight; + if (!GetNext()) + aResult->width += aBodyFrame->mAdjustWidth; return NS_OK; } nsresult -nsTreeColumn::GetXInTwips(nsIFrame* aBodyFrame, nscoord* aResult) +nsTreeColumn::GetXInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult) { nsIFrame* frame = GetFrame(aBodyFrame); if (!frame) { @@ -141,7 +142,7 @@ nsTreeColumn::GetXInTwips(nsIFrame* aBodyFrame, nscoord* aResult) } nsresult -nsTreeColumn::GetWidthInTwips(nsIFrame* aBodyFrame, nscoord* aResult) +nsTreeColumn::GetWidthInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult) { nsIFrame* frame = GetFrame(aBodyFrame); if (!frame) { @@ -149,6 +150,8 @@ nsTreeColumn::GetWidthInTwips(nsIFrame* aBodyFrame, nscoord* aResult) return NS_ERROR_FAILURE; } *aResult = frame->GetRect().width; + if (!GetNext()) + *aResult += aBodyFrame->mAdjustWidth; return NS_OK; } diff --git a/layout/xul/base/src/tree/src/nsTreeColumns.h b/layout/xul/base/src/tree/src/nsTreeColumns.h index a7e649be5526..c5b7ebe19e60 100644 --- a/layout/xul/base/src/tree/src/nsTreeColumns.h +++ b/layout/xul/base/src/tree/src/nsTreeColumns.h @@ -45,6 +45,7 @@ #include "nsIContent.h" #include "nsIFrame.h" +class nsTreeBodyFrame; class nsTreeColumns; // This class is our column info. We use it to iterate our columns and to obtain @@ -62,17 +63,17 @@ public: protected: nsIFrame* GetFrame(); - nsIFrame* GetFrame(nsIFrame* aBodyFrame); + nsIFrame* GetFrame(nsTreeBodyFrame* aBodyFrame); /** * Returns a rect with x and width taken from the frame's rect and specified * y and height. May fail in case there's no frame for the column. */ - nsresult GetRect(nsIFrame* aBodyFrame, nscoord aY, nscoord aHeight, + nsresult GetRect(nsTreeBodyFrame* aBodyFrame, nscoord aY, nscoord aHeight, nsRect* aResult); - nsresult GetXInTwips(nsIFrame* aBodyFrame, nscoord* aResult); - nsresult GetWidthInTwips(nsIFrame* aBodyFrame, nscoord* aResult); + nsresult GetXInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult); + nsresult GetWidthInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult); void SetColumns(nsTreeColumns* aColumns) { mColumns = aColumns; } diff --git a/modules/libpr0n/public/imgILoader.idl b/modules/libpr0n/public/imgILoader.idl index 576cf4b1120e..3d7dc41c471f 100644 --- a/modules/libpr0n/public/imgILoader.idl +++ b/modules/libpr0n/public/imgILoader.idl @@ -58,7 +58,7 @@ interface nsISimpleEnumerator; * @version 0.3 * @see imagelib2 */ -[scriptable, uuid(a32826ff-9e56-4425-a811-97a8dba64ff5)] +[scriptable, uuid(d2f50c69-1064-4ce3-a92d-01dc5f5b4842)] interface imgILoader : nsISupports { /** @@ -81,7 +81,7 @@ interface imgILoader : nsISupports * make sure to Cancel() the resulting request before the observer * goes away. */ - imgIRequest loadImage(in nsIURI aURI, + [noscript] imgIRequest loadImage(in nsIURI aURI, in nsIURI aInitialDocumentURL, in nsIURI aReferrerURI, in nsILoadGroup aLoadGroup, @@ -107,7 +107,10 @@ interface imgILoader : nsISupports * reference cycles. This means that callers of loadImageWithChannel should * make sure to Cancel() the resulting request before the observer goes away. */ - imgIRequest loadImageWithChannel(in nsIChannel aChannel, in imgIDecoderObserver aObserver, in nsISupports cx, out nsIStreamListener aListener); + [noscript] imgIRequest loadImageWithChannel(in nsIChannel aChannel, + in imgIDecoderObserver aObserver, + in nsISupports cx, + out nsIStreamListener aListener); /** * Checks if a decoder for the an image with the given mime type is available diff --git a/modules/oji/src/Makefile.in b/modules/oji/src/Makefile.in index 73e099404e5c..777719429c6e 100644 --- a/modules/oji/src/Makefile.in +++ b/modules/oji/src/Makefile.in @@ -64,6 +64,7 @@ REQUIRES = xpcom \ xpconnect \ windowwatcher \ gfx \ + thebes \ content \ layout \ docshell \ diff --git a/modules/plugin/base/src/Makefile.in b/modules/plugin/base/src/Makefile.in index 87bcef02f922..e49cbd58422a 100644 --- a/modules/plugin/base/src/Makefile.in +++ b/modules/plugin/base/src/Makefile.in @@ -66,6 +66,7 @@ REQUIRES = xpcom \ unicharutil \ dom \ gfx \ + thebes \ content \ widget \ mimetype \ diff --git a/netwerk/base/src/nsURLHelper.cpp b/netwerk/base/src/nsURLHelper.cpp index a906eb5ad088..6f962c4a5045 100644 --- a/netwerk/base/src/nsURLHelper.cpp +++ b/netwerk/base/src/nsURLHelper.cpp @@ -50,6 +50,7 @@ #include "nsNetCID.h" #include "netCore.h" #include "prprf.h" +#include "prnetdb.h" //---------------------------------------------------------------------------- // Init/Shutdown @@ -887,12 +888,18 @@ net_IsValidHostName(const nsCSubstring &host) const char *end = host.EndReading(); // ctrl-chars and !\"#%&'()*,/;<=>?@\\^{|}\x7f // if one of these chars is found return false - return net_FindCharInSet(host.BeginReading(), end, - "\x01\x02\x03\x04\x05\x06\x07\x08" - "\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" - "\x11\x12\x13\x14\x15\x16\x17\x18" - "\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20" - "\x21\x22\x23\x25\x26\x27\x28\x29" - "\x2a\x2c\x2f\x3b\x3c\x3d\x3e" - "\x3f\x40\x5c\x5e\x7b\x7c\x7e\x7f") == end; + if (net_FindCharInSet(host.BeginReading(), end, + "\x01\x02\x03\x04\x05\x06\x07\x08" + "\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" + "\x11\x12\x13\x14\x15\x16\x17\x18" + "\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20" + "\x21\x22\x23\x25\x26\x27\x28\x29" + "\x2a\x2c\x2f\x3b\x3c\x3d\x3e" + "\x3f\x40\x5c\x5e\x7b\x7c\x7e\x7f") == end) + return PR_TRUE; + + // Might be a valid IPv6 link-local address containing a percent sign + nsCAutoString strhost(host); + PRNetAddr addr; + return PR_StringToNetAddr(strhost.get(), &addr) == PR_SUCCESS; } diff --git a/netwerk/protocol/data/src/nsDataChannel.cpp b/netwerk/protocol/data/src/nsDataChannel.cpp index b7e3e1ab5430..106e51e843b3 100644 --- a/netwerk/protocol/data/src/nsDataChannel.cpp +++ b/netwerk/protocol/data/src/nsDataChannel.cpp @@ -38,6 +38,7 @@ // data implementation #include "nsDataChannel.h" +#include "nsDataHandler.h" #include "nsNetUtil.h" #include "nsIPipe.h" #include "nsIInputStream.h" @@ -55,71 +56,22 @@ nsDataChannel::OpenContentStream(PRBool async, nsIInputStream **result) NS_ENSURE_TRUE(URI(), NS_ERROR_NOT_INITIALIZED); nsresult rv; - PRBool lBase64 = PR_FALSE; nsCAutoString spec; rv = URI()->GetAsciiSpec(spec); if (NS_FAILED(rv)) return rv; - // move past "data:" - char *buffer = (char *) strstr(spec.BeginWriting(), "data:"); - if (!buffer) { - // malformed uri - return NS_ERROR_MALFORMED_URI; - } - buffer += 5; + nsCString contentType, contentCharset, dataBuffer; + PRBool lBase64; + rv = nsDataHandler::ParseURI(spec, contentType, contentCharset, + lBase64, dataBuffer); - // First, find the start of the data - char *comma = strchr(buffer, ','); - if (!comma) - return NS_ERROR_MALFORMED_URI; - - *comma = '\0'; - - // determine if the data is base64 encoded. - char *base64 = strstr(buffer, ";base64"); - if (base64) { - lBase64 = PR_TRUE; - *base64 = '\0'; - } - - nsCString contentType, contentCharset; - - if (comma == buffer) { - // nothing but data - contentType.AssignLiteral("text/plain"); - contentCharset.AssignLiteral("US-ASCII"); - } else { - // everything else is content type - char *semiColon = (char *) strchr(buffer, ';'); - if (semiColon) - *semiColon = '\0'; - - if (semiColon == buffer || base64 == buffer) { - // there is no content type, but there are other parameters - contentType.AssignLiteral("text/plain"); - } else { - contentType = buffer; - ToLowerCase(contentType); - } - - if (semiColon) { - char *charset = PL_strcasestr(semiColon + 1, "charset="); - if (charset) - contentCharset = charset + sizeof("charset=") - 1; - - *semiColon = ';'; - } - } - contentType.StripWhitespace(); - contentCharset.StripWhitespace(); - - nsCAutoString dataBuffer(comma + 1); NS_UnescapeURL(dataBuffer); - if (lBase64 || ((strncmp(contentType.get(),"text/",5) != 0) && - contentType.Find("xml") == kNotFound)) { - // it's ascii encoded binary, don't let any spaces in + if (lBase64) { + // Don't allow spaces in base64-encoded content. This is only + // relevant for escaped spaces; other spaces are stripped in + // NewURI. dataBuffer.StripWhitespace(); } @@ -136,7 +88,6 @@ nsDataChannel::OpenContentStream(PRBool async, nsIInputStream **result) PRUint32 contentLen; if (lBase64) { - *base64 = ';'; const PRUint32 dataLen = dataBuffer.Length(); PRInt32 resultLen = 0; if (dataLen >= 1 && dataBuffer[dataLen-1] == '=') { @@ -166,8 +117,6 @@ nsDataChannel::OpenContentStream(PRBool async, nsIInputStream **result) if (NS_FAILED(rv)) return rv; - *comma = ','; - SetContentType(contentType); SetContentCharset(contentCharset); SetContentLength64(contentLen); diff --git a/netwerk/protocol/data/src/nsDataHandler.cpp b/netwerk/protocol/data/src/nsDataHandler.cpp index 8c53b9943803..15c7b68b6825 100644 --- a/netwerk/protocol/data/src/nsDataHandler.cpp +++ b/netwerk/protocol/data/src/nsDataHandler.cpp @@ -46,6 +46,7 @@ #include "nsIInterfaceRequestorUtils.h" #include "nsIProgressEventSink.h" #include "nsNetCID.h" +#include "nsNetError.h" static NS_DEFINE_CID(kSimpleURICID, NS_SIMPLEURI_CID); @@ -101,11 +102,27 @@ nsDataHandler::NewURI(const nsACString &aSpec, nsIURI **result) { nsresult rv; + nsCString spec(aSpec); + nsCAutoString contentType, contentCharset, dataBuffer; + PRBool base64; + rv = ParseURI(spec, contentType, contentCharset, base64, dataBuffer); + if (NS_FAILED(rv)) + return rv; + + // Strip whitespace unless this is text, where whitespace is important + // Don't strip escaped whitespace though (bug 391951) + if (base64 || (strncmp(contentType.get(),"text/",5) != 0 && + contentType.Find("xml") == kNotFound)) { + // it's ascii encoded binary, don't let any spaces in + spec.StripWhitespace(); + } + + nsIURI* url; rv = CallCreateInstance(kSimpleURICID, &url); if (NS_FAILED(rv)) return rv; - rv = url->SetSpec(aSpec); + rv = url->SetSpec(spec); if (NS_FAILED(rv)) { NS_RELEASE(url); return rv; @@ -116,8 +133,7 @@ nsDataHandler::NewURI(const nsACString &aSpec, } NS_IMETHODIMP -nsDataHandler::NewChannel(nsIURI* uri, nsIChannel* *result) -{ +nsDataHandler::NewChannel(nsIURI* uri, nsIChannel* *result) { NS_ENSURE_ARG_POINTER(uri); nsDataChannel* channel = new nsDataChannel(uri); if (!channel) @@ -135,9 +151,77 @@ nsDataHandler::NewChannel(nsIURI* uri, nsIChannel* *result) } NS_IMETHODIMP -nsDataHandler::AllowPort(PRInt32 port, const char *scheme, PRBool *_retval) -{ +nsDataHandler::AllowPort(PRInt32 port, const char *scheme, PRBool *_retval) { // don't override anything. *_retval = PR_FALSE; return NS_OK; } + +nsresult +nsDataHandler::ParseURI(nsCString& spec, + nsCString& contentType, + nsCString& contentCharset, + PRBool& isBase64, + nsCString& dataBuffer) { + isBase64 = PR_FALSE; + + // move past "data:" + char *buffer = (char *) strstr(spec.BeginWriting(), "data:"); + if (!buffer) { + // malformed uri + return NS_ERROR_MALFORMED_URI; + } + buffer += 5; + + // First, find the start of the data + char *comma = strchr(buffer, ','); + if (!comma) + return NS_ERROR_MALFORMED_URI; + + *comma = '\0'; + + // determine if the data is base64 encoded. + char *base64 = strstr(buffer, ";base64"); + if (base64) { + isBase64 = PR_TRUE; + *base64 = '\0'; + } + + if (comma == buffer) { + // nothing but data + contentType.AssignLiteral("text/plain"); + contentCharset.AssignLiteral("US-ASCII"); + } else { + // everything else is content type + char *semiColon = (char *) strchr(buffer, ';'); + if (semiColon) + *semiColon = '\0'; + + if (semiColon == buffer || base64 == buffer) { + // there is no content type, but there are other parameters + contentType.AssignLiteral("text/plain"); + } else { + contentType = buffer; + ToLowerCase(contentType); + } + + if (semiColon) { + char *charset = PL_strcasestr(semiColon + 1, "charset="); + if (charset) + contentCharset = charset + sizeof("charset=") - 1; + + *semiColon = ';'; + } + } + + *comma = ','; + if (isBase64) + *base64 = ';'; + + contentType.StripWhitespace(); + contentCharset.StripWhitespace(); + + dataBuffer.Assign(comma + 1); + + return NS_OK; +} diff --git a/netwerk/protocol/data/src/nsDataHandler.h b/netwerk/protocol/data/src/nsDataHandler.h index 0a0887bffd49..28c14e9babf6 100644 --- a/netwerk/protocol/data/src/nsDataHandler.h +++ b/netwerk/protocol/data/src/nsDataHandler.h @@ -55,6 +55,15 @@ public: // Define a Create method to be used with a factory: static NS_METHOD Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult); + + // Parse a data: URI and return the individual parts + // (the given spec will temporarily be modified but will be returned + // to the original before returning) + static NS_HIDDEN_(nsresult) ParseURI(nsCString& spec, + nsCString& contentType, + nsCString& contentCharset, + PRBool& isBase64, + nsCString& dataBuffer); }; #endif /* nsDataHandler_h___ */ diff --git a/netwerk/protocol/http/src/nsHttpChannel.cpp b/netwerk/protocol/http/src/nsHttpChannel.cpp index a3d9bbd5973a..5c0a77d8acdb 100644 --- a/netwerk/protocol/http/src/nsHttpChannel.cpp +++ b/netwerk/protocol/http/src/nsHttpChannel.cpp @@ -104,6 +104,8 @@ nsHttpChannel::nsHttpChannel() , mProxyAuthContinuationState(nsnull) , mAuthContinuationState(nsnull) , mStartPos(LL_MAXUINT) + , mPendingAsyncCallOnResume(nsnull) + , mSuspendCount(0) , mRedirectionLimit(gHttpHandler->RedirectionLimit()) , mIsPending(PR_FALSE) , mWasOpened(PR_FALSE) @@ -354,31 +356,53 @@ nsHttpChannel::AsyncAbort(nsresult status) mStatus = status; mIsPending = PR_FALSE; - // create a proxy for the listener.. - nsCOMPtr observer; - NS_NewRequestObserverProxy(getter_AddRefs(observer), mListener, - NS_GetCurrentThread()); - if (observer) { - observer->OnStartRequest(this, mListenerContext); - observer->OnStopRequest(this, mListenerContext, mStatus); - } - else { - NS_ERROR("unable to create request observer proxy"); - // XXX else, no proxy object manager... what do we do? - } - mListener = 0; - mListenerContext = 0; - + nsresult rv = AsyncCall(&nsHttpChannel::HandleAsyncNotifyListener); + // And if that fails? Callers ignore our return value anyway.... + // finally remove ourselves from the load group. if (mLoadGroup) mLoadGroup->RemoveRequest(this, nsnull, status); - return NS_OK; + return rv; +} + +void +nsHttpChannel::HandleAsyncNotifyListener() +{ + NS_PRECONDITION(!mPendingAsyncCallOnResume, "How did that happen?"); + + if (mSuspendCount) { + LOG(("Waiting until resume to do async notification [this=%p]\n", + this)); + mPendingAsyncCallOnResume = &nsHttpChannel::HandleAsyncNotifyListener; + return; + } + + DoNotifyListener(); +} + +void +nsHttpChannel::DoNotifyListener() +{ + if (mListener) { + mListener->OnStartRequest(this, mListenerContext); + mListener->OnStopRequest(this, mListenerContext, mStatus); + mListener = 0; + mListenerContext = 0; + } } void nsHttpChannel::HandleAsyncRedirect() { + NS_PRECONDITION(!mPendingAsyncCallOnResume, "How did that happen?"); + + if (mSuspendCount) { + LOG(("Waiting until resume to do async redirect [this=%p]\n", this)); + mPendingAsyncCallOnResume = &nsHttpChannel::HandleAsyncRedirect; + return; + } + nsresult rv = NS_OK; LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this)); @@ -393,12 +417,7 @@ nsHttpChannel::HandleAsyncRedirect() // OnStart/OnStop notifications. LOG(("ProcessRedirection failed [rv=%x]\n", rv)); mStatus = rv; - if (mListener) { - mListener->OnStartRequest(this, mListenerContext); - mListener->OnStopRequest(this, mListenerContext, mStatus); - mListener = 0; - mListenerContext = 0; - } + DoNotifyListener(); } } @@ -419,14 +438,18 @@ nsHttpChannel::HandleAsyncRedirect() void nsHttpChannel::HandleAsyncNotModified() { + NS_PRECONDITION(!mPendingAsyncCallOnResume, "How did that happen?"); + + if (mSuspendCount) { + LOG(("Waiting until resume to do async not-modified [this=%p]\n", + this)); + mPendingAsyncCallOnResume = &nsHttpChannel::HandleAsyncNotModified; + return; + } + LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this)); - if (mListener) { - mListener->OnStartRequest(this, mListenerContext); - mListener->OnStopRequest(this, mListenerContext, mStatus); - mListener = 0; - mListenerContext = 0; - } + DoNotifyListener(); CloseCacheEntry(); @@ -986,11 +1009,42 @@ nsHttpChannel::ProxyFailover() if (NS_FAILED(rv)) return rv; - return ReplaceWithProxy(pi); + // XXXbz so where does this codepath remove us from the loadgroup, + // exactly? + return DoReplaceWithProxy(pi); +} + +void +nsHttpChannel::HandleAsyncReplaceWithProxy() +{ + NS_PRECONDITION(!mPendingAsyncCallOnResume, "How did that happen?"); + + if (mSuspendCount) { + LOG(("Waiting until resume to do async proxy replacement [this=%p]\n", + this)); + mPendingAsyncCallOnResume = + &nsHttpChannel::HandleAsyncReplaceWithProxy; + return; + } + + nsresult status = mStatus; + + nsCOMPtr pi; + pi.swap(mTargetProxyInfo); + if (!mCanceled) { + status = DoReplaceWithProxy(pi); + if (mLoadGroup && NS_SUCCEEDED(status)) { + mLoadGroup->RemoveRequest(this, nsnull, mStatus); + } + } + + if (NS_FAILED(status)) { + AsyncAbort(status); + } } nsresult -nsHttpChannel::ReplaceWithProxy(nsIProxyInfo *pi) +nsHttpChannel::DoReplaceWithProxy(nsIProxyInfo* pi) { nsresult rv; @@ -3335,25 +3389,39 @@ nsHttpChannel::Cancel(nsresult status) NS_IMETHODIMP nsHttpChannel::Suspend() { + NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE); + LOG(("nsHttpChannel::Suspend [this=%x]\n", this)); + + ++mSuspendCount; + if (mTransactionPump) return mTransactionPump->Suspend(); if (mCachePump) return mCachePump->Suspend(); - return NS_ERROR_UNEXPECTED; + return NS_OK; } NS_IMETHODIMP nsHttpChannel::Resume() { + NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED); + LOG(("nsHttpChannel::Resume [this=%x]\n", this)); + + if (--mSuspendCount == 0 && mPendingAsyncCallOnResume) { + nsresult rv = AsyncCall(mPendingAsyncCallOnResume); + mPendingAsyncCallOnResume = nsnull; + NS_ENSURE_SUCCESS(rv, rv); + } + if (mTransactionPump) return mTransactionPump->Resume(); if (mCachePump) return mCachePump->Resume(); - return NS_ERROR_UNEXPECTED; + return NS_OK; } NS_IMETHODIMP @@ -4170,17 +4238,8 @@ nsHttpChannel::OnProxyAvailable(nsICancelable *request, nsIURI *uri, // Need to replace this channel with a new one. It would be complex to try // to change the value of mConnectionInfo since so much of our state may // depend on its state. - if (!mCanceled) { - status = ReplaceWithProxy(pi); - - // XXX(darin): It'd be nice if removing ourselves from the loadgroup - // could be factored into ReplaceWithProxy somehow. - if (mLoadGroup && NS_SUCCEEDED(status)) - mLoadGroup->RemoveRequest(this, nsnull, mStatus); - } - - if (NS_FAILED(status)) - AsyncAbort(status); + mTargetProxyInfo = pi; + HandleAsyncReplaceWithProxy(); return NS_OK; } diff --git a/netwerk/protocol/http/src/nsHttpChannel.h b/netwerk/protocol/http/src/nsHttpChannel.h index 676d855aace2..ac836f92b69a 100644 --- a/netwerk/protocol/http/src/nsHttpChannel.h +++ b/netwerk/protocol/http/src/nsHttpChannel.h @@ -149,6 +149,9 @@ private: PRBool RequestIsConditional(); nsresult Connect(PRBool firstTime = PR_TRUE); nsresult AsyncAbort(nsresult status); + // Send OnStartRequest/OnStopRequest to our listener, if any. + void HandleAsyncNotifyListener(); + void DoNotifyListener(); nsresult SetupTransaction(); void AddCookiesToRequest(); nsresult ApplyContentConversions(); @@ -168,7 +171,8 @@ private: // proxy specific methods nsresult ProxyFailover(); - nsresult ReplaceWithProxy(nsIProxyInfo *); + nsresult DoReplaceWithProxy(nsIProxyInfo *); + void HandleAsyncReplaceWithProxy(); nsresult ResolveProxy(); // cache specific methods @@ -268,6 +272,18 @@ private: nsCString mEntityID; PRUint64 mStartPos; + // Function pointer that can be set to indicate that we got suspended while + // waiting on an AsyncCall. When we get resumed we should AsyncCall this + // function. + nsAsyncCallback mPendingAsyncCallOnResume; + + // Proxy info to replace with + nsCOMPtr mTargetProxyInfo; + + // Suspend counter. This is used if someone tries to suspend/resume us + // before we have either a cache pump or a transaction pump. + PRUint32 mSuspendCount; + // redirection specific data. PRUint8 mRedirectionLimit; diff --git a/netwerk/test/httpserver/README b/netwerk/test/httpserver/README index 9e2d0b976c1b..d16b1b050c1d 100644 --- a/netwerk/test/httpserver/README +++ b/netwerk/test/httpserver/README @@ -78,6 +78,10 @@ calling server.stop() before the host application closes to ensure that all requests have completed. Things probably aren't going to break too horribly if you don't do this, but better safe than sorry. +MozJSHTTP makes no effort to time out requests, beyond any the socket itself +might or might not provide. I don't believe it provides any by default, but +I haven't verified this. + To be clear: the guarantee that nsIHttpServer.stop says implementations should make when possible (that .stop returns only when all pending requests have been serviced) cannot be made in a 1.8 environment; it can be made in a 1.9 diff --git a/netwerk/test/httpserver/TODO b/netwerk/test/httpserver/TODO index e21dbdbaa614..3a9546611776 100644 --- a/netwerk/test/httpserver/TODO +++ b/netwerk/test/httpserver/TODO @@ -5,11 +5,6 @@ Bugs to fix: a performance standpoint?) Ideas for future improvements: -- mod_cern_meta or Apache asis functionality (probably the former, since - asis+binary files looks annoying but is a definite want, and line endings are - likely a pain): - and - - add API to disable response buffering which, when called, causes errors when you try to do anything other than write to the body stream (i.e., modify headers or status line) once you've written anything to it -- useful when diff --git a/netwerk/test/httpserver/httpd.js b/netwerk/test/httpserver/httpd.js index c3d643288cf0..6e702a3e8aa7 100644 --- a/netwerk/test/httpserver/httpd.js +++ b/netwerk/test/httpserver/httpd.js @@ -81,6 +81,13 @@ function HttpError(code, description) this.code = code; this.description = description; } +HttpError.prototype = +{ + toString: function() + { + return this.code + " " + this.description; + } +}; /** * Errors thrown to trigger specific HTTP server responses. @@ -158,12 +165,30 @@ function dumpn(str) dump(str + "\n"); } +/** Dumps the current JS stack if DEBUG. */ +function dumpStack() +{ + // peel off the frames for dumpStack() and Error() + var stack = new Error().stack.split(/\n/).slice(2); + stack.forEach(dumpn); +} + + +/** The XPCOM thread manager. */ +var gThreadManager = null; + /** * JavaScript constructors for commonly-used classes; precreating these is a * speedup over doing the same from base principles. See the docs at * http://developer.mozilla.org/en/docs/Components.Constructor for details. */ +const ServerSocket = CC("@mozilla.org/network/server-socket;1", + "nsIServerSocket", + "init"); +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); const Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init"); @@ -173,6 +198,9 @@ const FileInputStream = CC("@mozilla.org/network/file-input-stream;1", const StreamCopier = CC("@mozilla.org/network/async-stream-copier;1", "nsIAsyncStreamCopier", "init"); +const Pump = CC("@mozilla.org/network/input-stream-pump;1", + "nsIInputStreamPump", + "init"); const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1", "nsIConverterInputStream", "init"); @@ -185,10 +213,10 @@ const SupportsString = CC("@mozilla.org/supports-string;1", /** * Returns the RFC 822/1123 representation of a date. * - * @param date + * @param date : Number * the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT - * @returns - * the string specifying the given date + * @returns string + * the representation of the given date */ function toDateString(date) { @@ -213,9 +241,9 @@ function toDateString(date) * Processes a date and returns the encoded UTC time as a string according to * the format specified in RFC 2616. * - * @param date - * the date as a JavaScript Date object - * @returns + * @param date : Date + * the date to process + * @returns string * a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59" */ function toTime(date) @@ -238,9 +266,9 @@ function toDateString(date) * Processes a date and returns the encoded UTC date as a string according to * the date1 format specified in RFC 2616. * - * @param date - * the date as a JavaScript Date object - * @returns + * @param date : Date + * the date to process + * @returns string * a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59" */ function toDate1(date) @@ -267,7 +295,7 @@ function toDateString(date) /** * Prints out a human-readable representation of the object o and its fields, * omitting those whose names begin with "_" if showMembers != true (to ignore - * hidden properties exposed via getters/setters). + * "private" properties exposed via getters/setters). */ function printObj(o, showMembers) { @@ -289,6 +317,9 @@ function printObj(o, showMembers) */ function nsHttpServer() { + if (!gThreadManager) + gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService(); + /** The port on which this server listens. */ this._port = undefined; @@ -314,28 +345,51 @@ nsHttpServer.prototype = // NSISERVERSOCKETLISTENER /** - * This function signals that a new connection has been accepted. It is the - * method through which all requests must be handled, and by the end of this - * method any and all response requests must be sent. + * Processes an incoming request coming in on the given socket and contained + * in the given transport. * + * @param socket : nsIServerSocket + * the socket through which the request was served + * @param trans : nsISocketTransport + * the transport for the request/response * @see nsIServerSocketListener.onSocketAccepted */ - onSocketAccepted: function(serverSocket, trans) + onSocketAccepted: function(socket, trans) { + dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ") " + + "on thread " + gThreadManager.currentThread + + " (main is " + gThreadManager.mainThread + ")"); + dumpn(">>> new connection on " + trans.host + ":" + trans.port); - var input = trans.openInputStream(Ci.nsITransport.OPEN_BLOCKING, 0, 0); + const SEGMENT_SIZE = 8192; + const SEGMENT_COUNT = 1024; + var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT) + .QueryInterface(Ci.nsIAsyncInputStream); var output = trans.openOutputStream(Ci.nsITransport.OPEN_BLOCKING, 0, 0); - this._processConnection(serverSocket.port, input, output); + var conn = new Connection(input, output, this, socket.port); + var reader = new RequestReader(conn); + + // XXX add request timeout functionality here! + + // Note: must use main thread here, or we might get a GC that will cause + // threadsafety assertions. We really need to fix XPConnect so that + // you can actually do things in multi-threaded JS. :-( + input.asyncWait(reader, 0, 0, gThreadManager.mainThread); }, /** * Called when the socket associated with this is closed. * + * @param socket : nsIServerSocket + * the socket being closed + * @param status : nsresult + * the reason the socket stopped listening (NS_BINDING_ABORTED if the server + * was stopped using nsIHttpServer.stop) * @see nsIServerSocketListener.onStopListening */ - onStopListening: function(serverSocket, status) + onStopListening: function(socket, status) { dumpn(">>> shutting down server"); this._socketClosed = true; @@ -354,11 +408,9 @@ nsHttpServer.prototype = this._port = port; this._doQuit = this._socketClosed = false; - var socket = Cc["@mozilla.org/network/server-socket;1"] - .createInstance(Ci.nsIServerSocket); - socket.init(this._port, - true, // loopback only - -1); // default number of pending connections + var socket = new ServerSocket(this._port, + true, // loopback only + -1); // default number of pending connections dumpn(">>> listening on port " + socket.port); socket.asyncListen(this); @@ -378,18 +430,10 @@ nsHttpServer.prototype = this._socket = null; this._doQuit = false; - // spin an event loop and wait for the socket-close notification, if we can; - // this is only possible in Mozilla 1.9 code, but in situations where - // nothing really horrible happens at shutdown, nothing bad will happen in - // Mozilla 1.8-based code (and we want to support that) - if ("@mozilla.org/thread-manager;1" in Cc) - { - var thr = Cc["@mozilla.org/thread-manager;1"] - .getService() - .currentThread; - while (!this._socketClosed || this._handler.hasPendingRequests()) - thr.processNextEvent(true); - } + // spin an event loop and wait for the socket-close notification + var thr = gThreadManager.currentThread; + while (!this._socketClosed || this._handler.hasPendingRequests()) + thr.processNextEvent(true); }, // @@ -460,6 +504,7 @@ nsHttpServer.prototype = throw Cr.NS_ERROR_NO_INTERFACE; }, + // NON-XPCOM PUBLIC API /** @@ -471,75 +516,33 @@ nsHttpServer.prototype = { return this._socketClosed && !this._handler.hasPendingRequests(); }, + // PRIVATE IMPLEMENTATION /** - * Processes an incoming request in inStream served through the given port and - * writes the response to outStream. + * Closes the passed-in connection. * - * @param port - * the port on which the request was served - * @param inStream - * an nsIInputStream containing the incoming request - * @param outStream - * the nsIOutputStream to which the response should be written + * @param connection : Connection + * the connection to close */ - _processConnection: function(port, inStream, outStream) - { - try - { - var metadata = new RequestMetadata(port); - metadata.init(inStream); - - inStream.close(); - - this._handler.handleRequest(outStream, metadata); - } - catch (e) - { - dumpn(">>> internal error, shutting down server: " + e); - dumpn("*** stack trace: " + e.stack); - - inStream.close(); // in case we failed before then - - this._doQuit = true; - this._endConnection(this._handler, outStream); - } - - // stream cleanup, etc. happens in _endConnection below -- called from - // stream copier observer when all data has been copied - }, - - /** - * Closes the passed-in output stream and (if necessary) shuts down this - * server. - * - * @param handler - * the request handler which handled the request - * @param outStream - * the nsIOutputStream for the processed request which must be closed - */ - _endConnection: function(handler, outStream) + _endConnection: function(connection) { // // Order is important below: we must decrement handler._pendingRequests - // BEFORE calling this.stop(), if needed, because this.stop() returns only - // when the server socket's closed AND all pending requests are complete, - // which clearly isn't (and never will be) the case if it were the other way - // around + // BEFORE calling this.stop(), if needed, in connection.destroy(). + // this.stop() returns only when the server socket's closed AND all pending + // requests are complete, which clearly isn't (and never will be) the case + // if it were the other way around. // - outStream.close(); // inputstream already closed after processing - handler._pendingRequests--; + connection.close(); - // handle possible server quit -- note that this doesn't affect extant open - // connections and pending requests: nsIServerSocket.close is specified not - // to affect open connections, and nsIHttpServer.stop attempts to do its - // best to return only when the server socket is closed and all pending - // requests have been served - if (this._doQuit) - this.stop(); + NS_ASSERT(this == connection.server); + + this._handler._pendingRequests--; + + connection.destroy(); }, /** @@ -548,37 +551,715 @@ nsHttpServer.prototype = _requestQuit: function() { dumpn(">>> requesting a quit"); + dumpStack(); this._doQuit = true; } }; +/** + * Represents a connection to the server (and possibly in the future the thread + * on which the connection is processed). + * + * @param input : nsIInputStream + * stream from which incoming data on the connection is read + * @param output : nsIOutputStream + * stream to write data out the connection + * @param server : nsHttpServer + * the server handling the connection + * @param port : int + * the port on which the server is running + */ +function Connection(input, output, server, port) +{ + /** Stream of incoming data. */ + this.input = input; + + /** Stream for outgoing data. */ + this.output = output; + + /** The server associated with this request. */ + this.server = server; + + /** The port on which the server is running. */ + this.port = port; + + /** State variables for debugging. */ + this._closed = this._processed = false; +} +Connection.prototype = +{ + /** Closes this connection's input/output streams. */ + close: function() + { + this.input.close(); + this.output.close(); + this._closed = true; + }, + + /** + * Initiates processing of this connection, using the data in the given + * request. + * + * @param request : Request + * the request which should be processed + */ + process: function(request) + { + NS_ASSERT(!this._closed && !this._processed); + + this._processed = true; + + this.server._handler.handleResponse(this, request); + }, + + /** + * Initiates processing of this connection, generating a response with the + * given HTTP error code. + * + * @param code : uint + * an HTTP code, so in the range [0, 1000) + * @param metadata : Request + * incomplete data about the incoming request (since there were errors + * during its processing + */ + processError: function(code, metadata) + { + NS_ASSERT(!this._closed && !this._processed); + + this._processed = true; + + this.server._handler.handleError(code, this, metadata); + }, + + /** Ends this connection, destroying the resources it uses. */ + end: function() + { + this.server._endConnection(this); + }, + + /** Destroys resources used by this. */ + destroy: function() + { + if (!this._closed) + this.close(); + + // If an error triggered a server shutdown, act on it now + var server = this.server; + if (server._doQuit) + server.stop(); + } +}; + + + +/** Returns an array of count bytes from the given input stream. */ +function readBytes(inputStream, count) +{ + return new BinaryInputStream(inputStream).readByteArray(count); +} + + + +/** Request reader processing states; see RequestReader for details. */ +const READER_INITIAL = 0; +const READER_IN_HEADERS = 1; +const READER_IN_BODY = 2; + + +/** + * Reads incoming request data asynchronously, does any necessary preprocessing, + * and forwards it to the request handler. Processing occurs in three states: + * + * READER_INITIAL Haven't read the entire request line yet + * READER_IN_HEADERS Reading headers in the request + * READER_IN_BODY Finished reading all request headers (when body + * support's added, will be reading the body) + * + * During the first two stages, initial metadata about the request is gathered + * into a Request object. Once the status line and headers have been processed, + * we create a Response and hand it off to the ServerHandler to be given to the + * appropriate request handler. + * + * XXX we should set up a stream to provide lazy access to the request body + * + * @param connection : Connection + * the connection for the request being read + */ +function RequestReader(connection) +{ + /** Connection metadata for this request. */ + this._connection = connection; + + /** + * A container providing line-by-line access to the raw bytes that make up the + * data which has been read from the connection but has not yet been acted + * upon (by passing it to the request handler or by extracting request + * metadata from it). + */ + this._data = new LineData(); + + /** The current state of parsing the incoming request. */ + this._state = READER_INITIAL; + + /** Metadata constructed from the incoming request for the request handler. */ + this._metadata = new Request(connection.port); + + /** + * Used to preserve state if we run out of line data midway through a + * multi-line header. _lastHeaderName stores the name of the header, while + * _lastHeaderValue stores the value we've seen so far for the header. + * + * These fields are always either both undefined or both strings. + */ + this._lastHeaderName = this._lastHeaderValue = undefined; +} +RequestReader.prototype = +{ + // NSIINPUTSTREAMCALLBACK + + /** + * Called when more data from the incoming request is available. This method + * then reads the available data from input and deals with that data as + * necessary, depending upon the syntax of already-downloaded data. + * + * @param input : nsIAsyncInputStream + * the stream of incoming data from the connection + */ + onInputStreamReady: function(input) + { + dumpn("*** onInputStreamReady(input=" + input + ") on thread " + + gThreadManager.currentThread + " (main is " + + gThreadManager.mainThread + ")"); + dumpn("*** this._state == " + this._state); + + var count = input.available(); + + // Handle cases where we get more data after a request error has been + // discovered but *before* we can close the connection. + if (!this._data) + return; + + var moreAvailable = false; + + switch (this._state) + { + case READER_INITIAL: + moreAvailable = this._processRequestLine(input, count); + break; + + case READER_IN_HEADERS: + moreAvailable = this._processHeaders(input, count); + break; + + case READER_IN_BODY: + // XXX handle the request body! until then, just stop reading + break; + + default: + NS_ASSERT(false); + } + + if (moreAvailable) + input.asyncWait(this, 0, 0, gThreadManager.currentThread); + }, + + // + // see nsISupports.QueryInterface + // + QueryInterface: function(aIID) + { + if (aIID.equals(Ci.nsIInputStreamCallback) || + aIID.equals(Ci.nsISupports)) + return this; + + throw Cr.NS_ERROR_NO_INTERFACE; + }, + + + // PRIVATE API + + /** + * Reads count bytes from input and processes unprocessed, downloaded data as + * a request line. + * + * @param input : nsIInputStream + * stream from which count bytes of data must be read + * @param count : PRUint32 + * the number of bytes of data which must be read from input + * @returns boolean + * true if more data must be read from the request, false otherwise + */ + _processRequestLine: function(input, count) + { + NS_ASSERT(this._state == READER_INITIAL); + + var data = this._data; + data.appendBytes(readBytes(input, count)); + + + // servers SHOULD ignore any empty line(s) received where a Request-Line + // is expected (section 4.1) + var line = {}; + var readSuccess; + while ((readSuccess = data.readLine(line)) && line.value == "") + dumpn("*** ignoring beginning blank line..."); + + // if we don't have a full line, wait until we do + if (!readSuccess) + return true; + + // we have the first non-blank line + try + { + this._parseRequestLine(line.value); + + // do we have more header data to read? + if (!this._parseHeaders()) + return true; + + // headers complete, do a data check and then forward to the handler + this._validateRequest(); + return this._handleResponse(); + } + catch (e) + { + this._handleError(e); + return false; + } + }, + + /** + * Reads data from input and processes it, assuming it is either at the + * beginning or in the middle of processing request headers. + * + * @param input : nsIInputStream + * stream from which count bytes of data must be read + * @param count : PRUint32 + * the number of bytes of data which must be read from input + * @returns boolean + * true if more data must be read from the request, false otherwise + */ + _processHeaders: function(input, count) + { + NS_ASSERT(this._state == READER_IN_HEADERS); + + // XXX things to fix here: + // + // - need to support RFC 2047-encoded non-US-ASCII characters + // - really support absolute URLs (requires telling the server all its + // hostnames, beyond just localhost:port or 127.0.0.1:port) + + this._data.appendBytes(readBytes(input, count)); + + try + { + // do we have all the headers? + if (!this._parseHeaders()) + return true; + + // we have all the headers, continue with the body + this._validateRequest(); + return this._handleResponse(); + } + catch (e) + { + this._handleError(e); + return false; + } + }, + + /** + * Does various post-header checks on the data in this request. + * + * @throws : HttpError + * if the request was malformed in some way + */ + _validateRequest: function() + { + NS_ASSERT(this._state == READER_IN_BODY); + + dumpn("*** _validateRequest"); + + var metadata = this._metadata; + var headers = metadata._headers; + + var isHttp11 = metadata._httpVersion.equals(nsHttpVersion.HTTP_1_1); + + // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header + if (isHttp11 && !headers.hasHeader("Host")) + throw HTTP_400; + }, + + /** + * Handles responses in case of error, either in the server or in the request. + * + * @param e + * the specific error encountered, which is an HttpError in the case where + * the request is in some way invalid or cannot be fulfilled; if this isn't + * an HttpError we're going to be paranoid and shut down, because that + * shouldn't happen, ever + */ + _handleError: function(e) + { + var server = this._connection.server; + if (e instanceof HttpError) + { + var code = e.code; + } + else + { + // no idea what happened -- be paranoid and shut down + code = 500; + server._requestQuit(); + } + + // make attempted reuse of data an error + this._data = null; + + this._connection.processError(code, this._metadata); + }, + + /** + * Now that we've read the request line and headers, we can actually hand off + * the request to be handled. + * + * This method is called once per request, after the request line and all + * headers have been received. + * + * @returns boolean + * true if more data must be read, false otherwise + */ + _handleResponse: function() + { + NS_ASSERT(this._state == READER_IN_BODY); + + // XXX set up a stream for data in the request body here + + // We don't need the line-based data any more, so make attempted reuse an + // error. + this._data = null; + + this._connection.process(this._metadata); + + return false; + }, + + + // PARSING + + /** + * Parses the request line for the HTTP request associated with this. + * + * @param line : string + * the request line + */ + _parseRequestLine: function(line) + { + NS_ASSERT(this._state == READER_INITIAL); + + dumpn("*** _parseRequestLine('" + line + "')"); + + var metadata = this._metadata; + + // clients and servers SHOULD accept any amount of SP or HT characters + // between fields, even though only a single SP is required (section 19.3) + var request = line.split(/[ \t]+/); + if (!request || request.length != 3) + throw HTTP_400; + + metadata._method = request[0]; + + // get the HTTP version + var ver = request[2]; + var match = ver.match(/^HTTP\/(\d+\.\d+)$/); + if (!match) + throw HTTP_400; + + // determine HTTP version + try + { + metadata._httpVersion = new nsHttpVersion(match[1]); + if (!metadata._httpVersion.equals(nsHttpVersion.HTTP_1_0) && + !metadata._httpVersion.equals(nsHttpVersion.HTTP_1_1)) + throw "unsupported HTTP version"; + } + catch (e) + { + // we support HTTP/1.0 and HTTP/1.1 only + throw HTTP_501; + } + + + var fullPath = request[1]; + + if (fullPath.charAt(0) != "/") + { + // XXX we don't really support absolute URIs yet -- a MUST for HTTP/1.1; + // for now just get the path and use that, ignoring hostport + try + { + var uri = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService) + .newURI(fullPath, null, null); + fullPath = uri.path; + } + catch (e) { /* invalid URI */ } + if (fullPath.charAt(0) != "/") + { + this.errorCode = 400; + return; + } + } + + var splitter = fullPath.indexOf("?"); + if (splitter < 0) + { + // _queryString already set in ctor + metadata._path = fullPath; + } + else + { + metadata._path = fullPath.substring(0, splitter); + metadata._queryString = fullPath.substring(splitter + 1); + } + + // our work here is finished + this._state = READER_IN_HEADERS; + }, + + /** + * Parses all available HTTP headers in this until the header-ending CRLFCRLF, + * adding them to the store of headers in the request. + * + * @throws + * HTTP_400 if the headers are malformed + * @returns boolean + * true if all headers have now been processed, false otherwise + */ + _parseHeaders: function() + { + NS_ASSERT(this._state == READER_IN_HEADERS); + + dumpn("*** _parseHeaders"); + + var data = this._data; + + var headers = this._metadata._headers; + var lastName = this._lastHeaderName; + var lastVal = this._lastHeaderValue; + + var line = {}; + while (true) + { + NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)), + lastName === undefined ? + "lastVal without lastName? lastVal: '" + lastVal + "'" : + "lastName without lastVal? lastName: '" + lastName + "'"); + + if (!data.readLine(line)) + { + // save any data we have from the header we might still be processing + this._lastHeaderName = lastName; + this._lastHeaderValue = lastVal; + return false; + } + + var lineText = line.value; + var firstChar = lineText.charAt(0); + + // blank line means end of headers + if (lineText == "") + { + // we're finished with the previous header + if (lastName) + { + try + { + headers.setHeader(lastName, lastVal, true); + } + catch (e) + { + dumpn("*** e == " + e); + throw HTTP_400; + } + } + else + { + // no headers in request -- valid for HTTP/1.0 requests + } + + // either way, we're done processing headers + this._state = READER_IN_BODY; + return true; + } + else if (firstChar == " " || firstChar == "\t") + { + // multi-line header if we've already seen a header line + if (!lastName) + { + // we don't have a header to continue! + throw HTTP_400; + } + + // append this line's text to the value; starts with SP/HT, so no need + // for separating whitespace + lastVal += lineText; + } + else + { + // we have a new header, so set the old one (if one existed) + if (lastName) + { + try + { + headers.setHeader(lastName, lastVal, true); + } + catch (e) + { + dumpn("*** e == " + e); + throw HTTP_400; + } + } + + var colon = lineText.indexOf(":"); // first colon must be splitter + if (colon < 1) + { + // no colon or missing header field-name + throw HTTP_400; + } + + // set header name, value (to be set in the next loop, usually) + lastName = lineText.substring(0, colon); + lastVal = lineText.substring(colon + 1); + } // empty, continuation, start of header + } // while (true) + } +}; + + +/** The character codes for CR and LF. */ +const CR = 0x0D, LF = 0x0A; + +/** + * Calculates the number of characters before the first CRLF pair in array, or + * -1 if the array contains no CRLF pair. + * + * @param array : Array + * an array of numbers in the range [0, 256), each representing a single + * character; the first CRLF is the lowest index i where + * |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|, + * if such an |i| exists, and -1 otherwise + * @returns int + * the index of the first CRLF if any were present, -1 otherwise + */ +function findCRLF(array) +{ + for (var i = array.indexOf(CR); i >= 0; i = array.indexOf(CR, i + 1)) + { + if (array[i + 1] == LF) + return i; + } + return -1; +} + + +/** + * A container which provides line-by-line access to the arrays of bytes with + * which it is seeded. + */ +function LineData() +{ + /** An array of queued bytes from which to get line-based characters. */ + this._data = []; +} +LineData.prototype = +{ + /** + * Appends the bytes in the given array to the internal data cache maintained + * by this. + */ + appendBytes: function(bytes) + { + Array.prototype.push.apply(this._data, bytes); + }, + + /** + * Removes and returns a line of data, delimited by CRLF, from this. + * + * @param out + * an object whose "value" property will be set to the first line of text + * present in this, sans CRLF, if this contains a full CRLF-delimited line + * of text; if this doesn't contain enough data, the value of the property + * is undefined + * @returns boolean + * true if a full line of data could be read from the data in this, false + * otherwise + */ + readLine: function(out) + { + var data = this._data; + var length = findCRLF(data); + if (length < 0) + return false; + + // + // We have the index of the CR, so remove all the characters, including + // CRLF, from the array with splice, and convert the removed array into the + // corresponding string, from which we then strip the trailing CRLF. + // + // Getting the line in this matter acknowledges that substring is an O(1) + // operation in SpiderMonkey because strings are immutable, whereas two + // splices, both from the beginning of the data, are less likely to be as + // cheap as a single splice plus two extra character conversions. + // + var line = String.fromCharCode.apply(null, data.splice(0, length + 2)); + out.value = line.substring(0, length); + + return true; + }, + + /** + * Retrieve any bytes we may have overread from the request's postdata. After + * this method is called, this must not be used in any way. + * + * @returns Array + * the bytes read past the CRLFCRLF at the end of request headers + */ + purge: function() + { + var data = this._data; + this._data = null; + return data; + } +}; + + + /** * Gets a content-type for the given file, as best as it is possible to do so. * - * @param file + * @param file : nsIFile * the nsIFile for which to get a file type - * @returns + * @returns string * the best content-type which can be determined for the file */ function getTypeFromFile(file) { try { - var type = Cc["@mozilla.org/uriloader/external-helper-app-service;1"] - .getService(Ci.nsIMIMEService) - .getTypeFromFile(file); + return Cc["@mozilla.org/uriloader/external-helper-app-service;1"] + .getService(Ci.nsIMIMEService) + .getTypeFromFile(file); } catch (e) { - type = "application/octet-stream"; + return "application/octet-stream"; } - return type; } - /** * Creates a request-handling function for an nsIHttpRequestHandler object. */ @@ -797,17 +1478,17 @@ function maybeAddHeaders(file, metadata, response) * support for HTTP error pages for various codes and fallback to HTTP 500 if * those codes fail for any reason. * - * @param srv - * the nsHttpServer in which this handler is being used + * @param server : nsHttpServer + * the server in which this handler is being used */ -function ServerHandler(srv) +function ServerHandler(server) { // FIELDS /** * The nsHttpServer instance associated with this handler. */ - this._server = srv; + this._server = server; /** * A variable used to ensure that all requests are fully complete before the @@ -831,7 +1512,7 @@ function ServerHandler(srv) * Custom request handlers for the server in which this resides. Path-handler * pairs are stored as property-value pairs in this property. * - * @see also ServerHandler.prototype._defaultPaths + * @see ServerHandler.prototype._defaultPaths */ this._overridePaths = {}; @@ -840,13 +1521,13 @@ function ServerHandler(srv) * resides. Path-handler pairs are stored as property-value pairs in this * property. * - * @see also ServerHandler.prototype._defaultErrors + * @see ServerHandler.prototype._defaultErrors */ this._overrideErrors = {}; /** - * Init our default handler for directories. The handler used to - * serve directories when no index file is present. + * The default handler for requests for directories, used to serve directories + * when no index file is present. */ this._indexHandler = defaultIndexHandler; } @@ -856,26 +1537,19 @@ ServerHandler.prototype = /** * Handles a request to this server, responding to the request appropriately - * and initiating server shutdown if necessary. If the request metadata - * specifies an error code, the appropriate error response will be sent. + * and initiating server shutdown if necessary. * - * @param outStream - * an nsIOutputStream to which a response should be written - * @param metadata + * This method never throws an exception. + * + * @param connection : Connection + * the connection for this request + * @param metadata : Request * request metadata as generated from the initial request */ - handleRequest: function(outStream, metadata) + handleResponse: function(connection, metadata) { var response = new Response(); - // handle any existing error codes - if (metadata.errorCode) - { - dumpn("*** errorCode == " + metadata.errorCode); - this._handleError(metadata, response); - return this._end(response, outStream); - } - var path = metadata.path; dumpn("*** path == " + path); @@ -894,8 +1568,12 @@ ServerHandler.prototype = { response.recycle(); - if (!(e instanceof HttpError) || e.code != 404) + if (!(e instanceof HttpError)) throw HTTP_500; + if (e.code != 404) + throw e; + + dumpn("*** default: " + (path in this._defaultPaths)); if (path in this._defaultPaths) this._defaultPaths[path](metadata, response); @@ -903,36 +1581,40 @@ ServerHandler.prototype = throw HTTP_404; } } - catch (e2) + catch (e) { - if (!(e2 instanceof HttpError)) + var errorCode = "internal"; + + try { - dumpn("*** internal error: e2 == " + e2); - throw e2; + if (!(e instanceof HttpError)) + throw e; + + errorCode = e.code; + dumpn("*** errorCode == " + errorCode); + + response.recycle(); + + this._handleError(errorCode, metadata, response); } + catch (e2) + { + dumpn("*** error handling " + errorCode + " error: " + + "e2 == " + e2 + ", shutting down server"); - var errorCode = e2.code; - dumpn("*** errorCode == " + errorCode); - - response.recycle(); - - metadata.errorCode = errorCode; - this._handleError(metadata, response); + response.destroy(); + connection.close(); + connection.server.stop(); + return; + } } - return this._end(response, outStream); + this._end(response, connection); }, - /** - * Associates a file with a server path so that it is returned by future - * requests to that path. - * - * @param path - * the path on the server, which must begin with a "/" - * @param file - * the nsILocalFile representing the file to be served; must not be a - * directory - */ + // + // see nsIHttpServer.registerFile + // registerFile: function(path, file) { dumpn("*** registering '" + path + "' as mapping to " + file.path); @@ -945,18 +1627,7 @@ ServerHandler.prototype = throw HTTP_404; response.setStatusLine(metadata.httpVersion, 200, "OK"); - - try - { - this._writeFileResponse(file, response); - } - catch (e) - { - // something happened -- but the calling code should handle the error - // and clean up the response - throw e; - } - + this._writeFileResponse(file, response); maybeAddHeaders(file, metadata, response); }; }, @@ -1028,12 +1699,26 @@ ServerHandler.prototype = this._indexHandler = handler; }, + // NON-XPCOM PUBLIC API + /** - * Set or remove a handler in an ojbect with a key. - * If handler is null, the key will be deleted. + * Returns true if this handler is in the middle of handling any current + * requests; this must be false before the server in which this is used may be + * safely shut down. + */ + hasPendingRequests: function() + { + return this._pendingRequests > 0; + }, + + + // PRIVATE API + + /** + * Sets or remove (if handler is null) a handler in an object with a key. * * @param handler - * A function or an nsIHttpRequestHandler object. + * a handler, either function or an nsIHttpRequestHandler * @param dict * The object to attach the handler to. * @param key @@ -1050,27 +1735,15 @@ ServerHandler.prototype = delete dict[key]; }, - /** - * Returns true if this handler is in the middle of handling any current - * requests; this must be false before the server in which this is used may be - * safely shut down. - */ - hasPendingRequests: function() - { - return this._pendingRequests > 0; - }, - - // PRIVATE API - /** * Handles a request which maps to a file in the local filesystem (if a base * path has already been set; otherwise the 404 error is thrown). * - * @param metadata - * request-related data as a RequestMetadata object - * @param response - * an uninitialized Response to the given request which must be initialized - * by a request handler + * @param metadata : Request + * metadata for the incoming request + * @param response : Response + * an uninitialized Response to the given request, to be initialized by a + * request handler * @throws HTTP_### * if an HTTP error occurred (usually HTTP_404); note that in this case the * calling code must handle cleanup of the response by calling .destroy() @@ -1078,54 +1751,47 @@ ServerHandler.prototype = */ _handleDefault: function(metadata, response) { - try + response.setStatusLine(metadata.httpVersion, 200, "OK"); + + var path = metadata.path; + NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">"); + + // determine the actual on-disk file; this requires finding the deepest + // path-to-directory mapping in the requested URL + var file = this._getFileForPath(path); + + // the "file" might be a directory, in which case we either serve the + // contained index.html or make the index handler write the response + if (file.exists() && file.isDirectory()) { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - - var path = metadata.path; - NS_ASSERT(path.charAt(0) == "/"); - - // determine the actual on-disk file; this requires finding the deepest - // path-to-directory mapping in the requested URL - var file = this._getFileForPath(path); - - // the "file" might be a directory, in which case we either serve the - // contained index.html or make the index handler write the response - if (file.exists() && file.isDirectory()) + file.append("index.html"); // make configurable? + if (!file.exists() || file.isDirectory()) { - file.append("index.html"); // make configurable? - if (!file.exists() || file.isDirectory()) - { - metadata._bag.setPropertyAsInterface("directory", file.parent); - this._indexHandler(metadata, response); - return; - } + metadata._ensurePropertyBag(); + metadata._bag.setPropertyAsInterface("directory", file.parent); + this._indexHandler(metadata, response); + return; } - - // alternately, the file might not exist - if (!file.exists()) - throw HTTP_404; - - // finally... - dumpn("*** handling '" + path + "' as mapping to " + file.path); - this._writeFileResponse(file, response); - - maybeAddHeaders(file, metadata, response); - } - catch (e) - { - // something failed, but make the calling code handle Response cleanup - throw e; } + + // alternately, the file might not exist + if (!file.exists()) + throw HTTP_404; + + // finally... + dumpn("*** handling '" + path + "' as mapping to " + file.path); + this._writeFileResponse(file, response); + + maybeAddHeaders(file, metadata, response); }, /** * Writes an HTTP response for the given file, including setting headers for * file metadata. * - * @param file + * @param file : nsILocalFile * the file which is to be sent in the response - * @param response + * @param response : Response * the response to which the file should be written */ _writeFileResponse: function(file, response) @@ -1152,13 +1818,14 @@ ServerHandler.prototype = * all registered path->directory mappings and any paths which are explicitly * overridden. * - * @returns - * the nsILocalFile which is to be sent as the response to a request for - * path + * @param path : string + * the server path for which a file should be retrieved, e.g. "/foo/bar" * @throws HttpError * when the correct action is the corresponding HTTP error (i.e., because no * mapping was found for a directory in path, the referenced file doesn't * exist, etc.) + * @returns nsILocalFile + * the file to be sent as the response to a request for the path */ _getFileForPath: function(path) { @@ -1237,27 +1904,55 @@ ServerHandler.prototype = return file; }, + /** + * Writes the error page for the given HTTP error code over the given + * connection. + * + * @param errorCode : uint + * the HTTP error code to be used + * @param connection : Connection + * the connection on which the error occurred + */ + handleError: function(errorCode, connection) + { + var response = new Response(); + + dumpn("*** error in request: " + errorCode); + + try + { + this._handleError(errorCode, new Request(connection.port), response); + this._end(response, connection); + } + catch (e) + { + connection.close(); + connection.server.stop(); + } + }, + /** * Handles a request which generates the given error code, using the * user-defined error handler if one has been set, gracefully falling back to * the x00 status code if the code has no handler, and failing to status code * 500 if all else fails. * - * @param metadata + * @param errorCode : uint + * the HTTP error which is to be returned + * @param metadata : Request * metadata for the request, which will often be incomplete since this is an - * error -- must have its .errorCode set for the desired error - * @param response + * error + * @param response : Response * an uninitialized Response should be initialized when this method * completes with information which represents the desired error code in the * ideal case or a fallback code in abnormal circumstances (i.e., 500 is a * fallback for 505, per HTTP specs) */ - _handleError: function(metadata, response) + _handleError: function(errorCode, metadata, response) { if (!metadata) throw Cr.NS_ERROR_NULL_POINTER; - var errorCode = metadata.errorCode; var errorX00 = errorCode - (errorCode % 100); try @@ -1324,167 +2019,182 @@ ServerHandler.prototype = * those headers and data, sends them to the HTTP client, and halts further * processing. It will also send a quit message to the server if necessary. * - * @param response - * a Response object representing the desired response - * @param outStream - * a stream to which the response should be written + * This method never throws an exception. + * + * @param response : Response + * the desired response + * @param connection : Connection + * the connection associated with the given response * @note * after completion, response must be considered "dead", and none of its * methods or properties may be accessed */ - _end: function(response, outStream) + _end: function(response, connection) { + // post-processing + response.setHeader("Connection", "close", false); + response.setHeader("Server", "MozJSHTTP", false); + response.setHeader("Date", toDateString(Date.now()), false); + + var bodyStream = response.bodyInputStream; + + // XXX suckage time! + // + // If the body of the response has had no data written to it (or has never + // been accessed -- same deal internally since we'll create one if we have + // to access bodyInputStream but have neither an input stream nor an + // output stream), the in-tree implementation of nsIPipe is such that + // when we try to close the pipe's output stream with no data in it, this + // is interpreted as an error and closing the output stream also closes + // the input stream. .available then throws, so we catch and deal as best + // as we can. + // + // Unfortunately, the easy alternative (substitute a storage stream for a + // pipe) also doesn't work. There's no problem writing zero bytes to the + // output end of the stream, but then attempting to get an input stream to + // read fails because the seek position must be strictly less than the + // buffer size. + // + // Much as I'd like the zero-byte body to be a mostly-unimportant problem, + // there are some HTTP responses such as 304 Not Modified which MUST have + // zero-byte bodies, so this *is* a necessary hack. try { - // post-processing - response.setHeader("Connection", "close", false); - response.setHeader("Server", "MozJSHTTP", false); - response.setHeader("Date", toDateString(Date.now()), false); - - var bodyStream = response.bodyInputStream - .QueryInterface(Ci.nsIInputStream); - - // XXX suckage time! - // - // If the body of the response has had no data written to it (or has never - // been accessed -- same deal internally since we'll create one if we have - // to access bodyInputStream but have neither an input stream nor an - // output stream), the in-tree implementation of nsIPipe is such that - // when we try to close the pipe's output stream with no data in it, this - // is interpreted as an error and closing the output stream also closes - // the input stream. .available then throws, so we catch and deal as best - // as we can. - // - // Unfortunately, the easy alternative (substitute a storage stream for a - // pipe) also doesn't work. There's no problem writing zero bytes to the - // output end of the stream, but then attempting to get an input stream to - // read fails because the seek position must be strictly less than the - // buffer size. - // - // Much as I'd like the zero-byte body to be a mostly-unimportant problem, - // there are some HTTP responses such as 304 Not Modified which MUST have - // zero-byte bodies, so this *is* a necessary hack. - try - { - var available = bodyStream.available(); - } - catch (e) - { - available = 0; - } - - response.setHeader("Content-Length", available.toString(), false); - - - // construct and send response - - // request-line - var preamble = "HTTP/" + response.httpVersion + " " + - response.httpCode + " " + - response.httpDescription + "\r\n"; - - // headers - var head = response.headers; - var headEnum = head.enumerator; - while (headEnum.hasMoreElements()) - { - var fieldName = headEnum.getNext() - .QueryInterface(Ci.nsISupportsString) - .data; - preamble += fieldName + ": " + head.getHeader(fieldName) + "\r\n"; - } - - // end request-line/headers - preamble += "\r\n"; - outStream.write(preamble, preamble.length); - - - // In certain situations, it's possible for us to have a race between - // the copy observer's onStopRequest and the listener for a channel - // opened to this server. Since we include a Content-Length header with - // every response, if the channel snarfs up all the data we promise, - // calls onStopRequest on the listener (and the server is shut down - // by that listener, causing the script to finish executing), and then - // tries to call onStopRequest on the copyObserver, we'll call into a - // scope with no Components and cause assertions *and* fail to close the - // connection properly. To combat this, during server shutdown we delay - // full shutdown until any pending requests are fully copied using this - // property on the server handler. We increment before (possibly) - // starting the copy observer and decrement when the copy completes, - // ensuring that all copies complete before the server fully shuts down. - // - // We do this for every request primarily to simplify maintenance of this - // property (and also because it's less fragile when we can remove the - // zero-sized body hack used above). - this._pendingRequests++; - - // If we have a body, send it -- if we don't, then don't bother with a - // heavyweight async copy which doesn't need to happen and just do - // response post-processing (usually handled by the copy observer) - // directly - if (available != 0) - { - // local references for use in the copy observer - var server = this._server; - var handler = this; - - /** - * Observer of the copying of data from the body stream generated by a - * request handler to the output stream for the server socket. It - * handles all post-request-writing cleanup details, such as closing - * open streams and shutting down the server in case of errors. - */ - var copyObserver = - { - onStartRequest: function(request, context) { /* don't care */ }, - - /** - * Called when the async stream copy completes. This is place where - * final cleanup should occur, including stream closures and - * response destruction. Note that errors which are detected here - * should still shut down the server, for safety. - */ - onStopRequest: function(request, context, statusCode) - { - // if something bad happened during the copy, be paranoid - if (!Components.isSuccessCode(statusCode)) - server._requestQuit(); - - // we're completely finished with this response - response.destroy(); - - server._endConnection(handler, outStream); - }, - - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIRequestObserver) || - aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } - }; - - - // body -- written async, because pipes deadlock if we do - // |outStream.writeFrom(bodyStream, bodyStream.available());| - var copier = new StreamCopier(bodyStream, outStream, null, - true, true, 8192); - copier.asyncCopy(copyObserver, null); - } - else - { - // finished with the response -- destroy - response.destroy(); - this._server._endConnection(this, outStream); - } + var available = bodyStream.available(); } catch (e) { - // something bad happened -- throw and make calling code deal with the - // response given to us - throw e; + available = 0; + } + + response.setHeader("Content-Length", available.toString(), false); + + + // construct and send response + + // request-line + var preamble = "HTTP/" + response.httpVersion + " " + + response.httpCode + " " + + response.httpDescription + "\r\n"; + + // headers + var head = response.headers; + var headEnum = head.enumerator; + while (headEnum.hasMoreElements()) + { + var fieldName = headEnum.getNext() + .QueryInterface(Ci.nsISupportsString) + .data; + preamble += fieldName + ": " + head.getHeader(fieldName) + "\r\n"; + } + + // end request-line/headers + preamble += "\r\n"; + + var outStream = connection.output; + try + { + outStream.write(preamble, preamble.length); + } + catch (e) + { + // Connection closed already? Even if not, failure to write the response + // means we probably will fail later anyway, so in the interests of + // avoiding exceptions we'll (possibly) close the connection and return. + response.destroy(); + connection.close(); + return; + } + + // In certain situations, it's possible for us to have a race between + // the copy observer's onStopRequest and the listener for a channel + // opened to this server. Since we include a Content-Length header with + // every response, if the channel snarfs up all the data we promise, + // calls onStopRequest on the listener (and the server is shut down + // by that listener, causing the script to finish executing), and then + // tries to call onStopRequest on the copyObserver, we'll call into a + // scope with no Components and cause assertions *and* fail to close the + // connection properly. To combat this, during server shutdown we delay + // full shutdown until any pending requests are fully copied using this + // property on the server handler. We increment before (possibly) + // starting the copy observer and decrement when the copy completes, + // ensuring that all copies complete before the server fully shuts down. + // + // We do this for every request primarily to simplify maintenance of this + // property (and also because it's less fragile when we can remove the + // zero-sized body hack used above). + this._pendingRequests++; + + var server = this._server; + + // If we have a body, send it -- if we don't, then don't bother with a + // heavyweight async copy which doesn't need to happen and just do + // response post-processing (usually handled by the copy observer) + // directly + if (available != 0) + { + /** + * Observer of the copying of data from the body stream generated by a + * request handler to the output stream for the server socket. It + * handles all post-request-writing cleanup details, such as closing + * open streams and shutting down the server in case of errors. + */ + var copyObserver = + { + onStartRequest: function(request, context) { /* don't care */ }, + + /** + * Called when the async stream copy completes. This is place where + * final cleanup should occur, including stream closures and + * response destruction. Note that errors which are detected here + * should still shut down the server, for safety. + */ + onStopRequest: function(request, cx, statusCode) + { + // statusCode can indicate user-triggered failures (e.g. if the user + // closes the connection during the copy, which would cause a status + // of NS_ERROR_NET_RESET), so don't treat its value being an error + // code as catastrophic. I can create this situation when running + // Mochitests in a debug build by clicking the Stop button during + // test execution, but it's not exactly a surefire way to reproduce + // the problem. + if (!Components.isSuccessCode(statusCode)) + { + dumpn("*** WARNING: non-success statusCode in onStopRequest: " + + statusCode); + } + + // we're completely finished with this response + response.destroy(); + + connection.end(); + }, + + QueryInterface: function(aIID) + { + if (aIID.equals(Ci.nsIRequestObserver) || + aIID.equals(Ci.nsISupports)) + return this; + + throw Cr.NS_ERROR_NO_INTERFACE; + } + }; + + + // + // Now write out the body, async since we might be serving this to + // ourselves on the same thread, and writing too much data would deadlock. + // + var copier = new StreamCopier(bodyStream, outStream, + null, + true, true, 8192); + copier.asyncCopy(copyObserver, null); + } + else + { + // finished with the response -- destroy + response.destroy(); + this._server._endConnection(connection); } }, @@ -1617,7 +2327,7 @@ ServerHandler.prototype = if (metadata.queryString) body += "?" + metadata.queryString; - body += " HTTP/" + metadata.httpVersion + "\n"; + body += " HTTP/" + metadata.httpVersion + "\r\n"; var headEnum = metadata.headers; while (headEnum.hasMoreElements()) @@ -1625,7 +2335,7 @@ ServerHandler.prototype = var fieldName = headEnum.getNext() .QueryInterface(Ci.nsISupportsString) .data; - body += fieldName + ": " + metadata.getHeader(fieldName) + "\n"; + body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n"; } response.bodyOutputStream.write(body, body.length); @@ -1649,6 +2359,11 @@ FileMap.prototype = /** * Maps key to a clone of the nsILocalFile value if value is non-null; * otherwise, removes any extant mapping for key. + * + * @param key : string + * string to which a clone of value is mapped + * @param value : nsILocalFile + * the file to map to key, or null to remove a mapping */ put: function(key, value) { @@ -1661,6 +2376,11 @@ FileMap.prototype = /** * Returns a clone of the nsILocalFile mapped to key, or null if no such * mapping exists. + * + * @param key : string + * key to which the returned file maps + * @returns nsILocalFile + * a clone of the mapped file, or null if no mapping exists */ get: function(key) { @@ -1704,9 +2424,9 @@ const IS_TOKEN_ARRAY = /** * Determines whether the given character code is a CTL. * - * @param code + * @param code : uint * the character code - * @returns + * @returns boolean * true if code is a CTL, false otherwise */ function isCTL(code) @@ -2003,7 +2723,7 @@ const headerUtils = * * @throws NS_ERROR_INVALID_ARG * if fieldName does not match the field-name production in RFC 2616 - * @returns + * @returns string * fieldName converted to lowercase if it is a valid header, for characters * where case conversion is possible */ @@ -2030,11 +2750,11 @@ const headerUtils = * part of the HTTP protocol), normalizes the value if it is, and * returns the normalized value. * - * @param fieldValue + * @param fieldValue : string * a value to be normalized as an HTTP header field value * @throws NS_ERROR_INVALID_ARG * if fieldValue does not match the field-value production in RFC 2616 - * @returns + * @returns string * fieldValue as a normalized HTTP header field value */ normalizeFieldValue: function(fieldValue) @@ -2078,9 +2798,9 @@ const headerUtils = * Converts the given string into a string which is safe for use in an HTML * context. * - * @param str + * @param str : string * the string to make HTML-safe - * @returns + * @returns string * an HTML-safe version of str */ function htmlEscape(str) @@ -2108,24 +2828,18 @@ function nsHttpVersion(versionString) if (!matches) throw "Not a valid HTTP version!"; + /** The major version number of this, as a number. */ this.major = parseInt(matches[1], 10); + + /** The minor version number of this, as a number. */ this.minor = parseInt(matches[2], 10); + if (isNaN(this.major) || isNaN(this.minor) || this.major < 0 || this.minor < 0) throw "Not a valid HTTP version!"; } nsHttpVersion.prototype = { - /** - * The major version number of this, as a number - */ - major: undefined, - - /** - * The minor version number of this, as a number. - */ - minor: undefined, - /** * Returns the standard string representation of the HTTP version represented * by this (e.g., "1.1"). @@ -2138,6 +2852,9 @@ nsHttpVersion.prototype = /** * Returns true if this represents the same HTTP version as otherVersion, * false otherwise. + * + * @param otherVersion : nsHttpVersion + * the version to compare against this */ equals: function (otherVersion) { @@ -2180,9 +2897,9 @@ nsHttpHeaders.prototype = /** * Sets the header represented by name and value in this. * - * @param name + * @param name : string * the header name - * @param value + * @param value : string * the header value * @throws NS_ERROR_INVALID_ARG * if name or value is not a valid header component @@ -2201,14 +2918,14 @@ nsHttpHeaders.prototype = /** * Returns the value for the header specified by this. * - * @returns - * the field value for the given header, possibly with non-semantic changes - * (i.e., leading/trailing whitespace stripped, whitespace runs replaced - * with spaces, etc.) at the option of the implementation * @throws NS_ERROR_INVALID_ARG * if fieldName does not constitute a valid header field name * @throws NS_ERROR_NOT_AVAILABLE * if the given header does not exist in this + * @returns string + * the field value for the given header, possibly with non-semantic changes + * (i.e., leading/trailing whitespace stripped, whitespace runs replaced + * with spaces, etc.) at the option of the implementation */ getHeader: function(fieldName) { @@ -2224,10 +2941,12 @@ nsHttpHeaders.prototype = * Returns true if a header with the given field name exists in this, false * otherwise. * - * @param fieldName + * @param fieldName : string * the field name whose existence is to be determined in this * @throws NS_ERROR_INVALID_ARG * if fieldName does not constitute a valid header field name + * @returns boolean + * true if the header's present, false otherwise */ hasHeader: function(fieldName) { @@ -2259,8 +2978,8 @@ nsHttpHeaders.prototype = /** * Constructs an nsISimpleEnumerator for the given array of items. * - * @param items - * the array of items, which must all implement nsISupports + * @param items : Array + * the items, which must all implement nsISupports */ function nsSimpleEnumerator(items) { @@ -2292,22 +3011,19 @@ nsSimpleEnumerator.prototype = /** - * Parses a server request into a set of metadata, so far as is possible. Any - * detected errors will result in this.errorCode being set to an HTTP error code - * value. Users MUST check this value after creation and any external - * initialization of RequestMetadata objects to ensure that errors are handled - * correctly. + * A representation of the data in an HTTP request. * - * @param port + * @param port : uint * the port on which the server receiving this request runs */ -function RequestMetadata(port) +function Request(port) { this._method = ""; this._path = ""; this._queryString = ""; this._host = ""; this._port = port; + this._host = "localhost"; // XXX or from environment or server itself? /** * The headers in this request. @@ -2315,20 +3031,13 @@ function RequestMetadata(port) this._headers = new nsHttpHeaders(); /** - * For the addition of ad-hoc properties and new functionality - * without having to tweak nsIHttpRequestMetadata every time. + * For the addition of ad-hoc properties and new functionality without having + * to change nsIHttpRequestMetadata every time; currently lazily created, + * as its only use is in directory listings. */ - this._bag = new WritablePropertyBag(); - - /** - * The numeric HTTP error, if any, associated with this request. This value - * may be set by the constructor but is usually only set by the handler after - * this has been constructed. After this has been initialized, this value - * MUST be checked for errors. - */ - this.errorCode = 0; + this._bag = null; } -RequestMetadata.prototype = +Request.prototype = { // SERVER METADATA @@ -2413,6 +3122,7 @@ RequestMetadata.prototype = // get enumerator() { + this._ensurePropertyBag(); return this._bag.enumerator; }, @@ -2421,262 +3131,15 @@ RequestMetadata.prototype = // getProperty: function(name) { + this._ensurePropertyBag(); return this._bag.getProperty(name); }, - - // ENTITY - - /** - * An input stream which contains the body of this request. - */ - get bodyStream() + + /** Ensures a property bag has been created for ad-hoc behaviors. */ + _ensurePropertyBag: function() { - // we want this once we do real request processing -- expose externally when - // we do this - return null; - }, - - // PUBLIC CONSTRUCTION API - - /** - * The HTTP error code which should be the result of this request. It must be - * checked whenever other API documentation says it should be checked. - */ - errorCode: 0, - - // INITIALIZATION - init: function(input) - { - // XXX this is incredibly un-RFC2616 in every possible way: - // - // - accepts non-CRLF line endings - // - no real testing for non-US-ASCII text and throwing in that case - // - handles POSTs by displaying the URL and throwing away the request - // entity - // - need to support RFC 2047-encoded non-US-ASCII characters - // - really support absolute URLs (requires telling the server its hostname, - // beyond just localhost:port and 127.0.0.1:port), not just pretend we - // serve every request that's given to us regardless of the server - // hostname and port - // - etc. - - // read the input line by line; the first line either tells us the requested - // path or is empty, in which case the second line contains the path - var lis = new ConverterInputStream(input, "ISO-8859-1", 1024, 0xFFFD); - lis.QueryInterface(Ci.nsIUnicharLineInputStream); - - - this._parseRequestLine(lis); - if (this.errorCode) - return; - - this._parseHeaders(lis); - if (this.errorCode) - return; - - // XXX need to put body transmitted with this request into an input stream! - - // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header - if (!this._headers.hasHeader("Host") && - this._httpVersion.equals(nsHttpVersion.HTTP_1_1)) - { - this.errorCode = 400; - return; - } - - // XXX set this based on Host or the request URI? - this._host = "localhost"; - }, - - // PRIVATE API - - /** - * Parses the request line for the HTTP request in the given input stream. On - * completion this.errorCode must be checked to determine whether any errors - * occurred during header parsing. - * - * @param lis - * an nsIUnicharLineInputStream from which to parse HTTP headers - */ - _parseRequestLine: function(lis) - { - // servers SHOULD ignore any empty line(s) received where a Request-Line - // is expected (section 4.1) - var line = {}; - while (lis.readLine(line) && line.value == "") - dumpn("*** ignoring beginning blank line..."); - - // clients and servers SHOULD accept any amount of SP or HT characters - // between fields, even though only a single SP is required (section 19.3) - var request = line.value.split(/[ \t]+/); - if (!request || request.length != 3) - { - this.errorCode = 400; - return; - } - - this._method = request[0]; - - // check the HTTP version - var ver = request[2]; - var match = ver.match(/^HTTP\/(\d+\.\d+)$/); - if (!match) - { - this.errorCode = 400; - return; - } - - // reject unrecognized methods - if (request[0] != "GET" && request[0] != "POST") - { - this.errorCode = 501; - return; - } - - // determine HTTP version - try - { - this._httpVersion = new nsHttpVersion(match[1]); - if (!this._httpVersion.equals(nsHttpVersion.HTTP_1_0) && - !this._httpVersion.equals(nsHttpVersion.HTTP_1_1)) - throw "unsupported HTTP version"; - } - catch (e) - { - // we support HTTP/1.0 and HTTP/1.1 only - this.errorCode = 501; - return; - } - - var fullPath = request[1]; - - if (fullPath.charAt(0) != "/") - { - // XXX we don't support absolute URIs yet -- a MUST for HTTP/1.1; - // for now just get the path and use that, ignoring hostport - try - { - var uri = Cc["@mozilla.org/network/io-service;1"] - .getService(Ci.nsIIOService) - .newURI(fullPath, null, null); - fullPath = uri.path; - } - catch (e) { /* invalid URI */ } - if (fullPath.charAt(0) != "/") - { - this.errorCode = 400; - return; - } - } - - var splitter = fullPath.indexOf("?"); - if (splitter < 0) - { - // _queryString already set in ctor - this._path = fullPath; - } - else - { - this._path = fullPath.substring(0, splitter); - this._queryString = fullPath.substring(splitter + 1); - } - }, - - /** - * Parses all available HTTP headers from the given input stream. On - * completion, this.errorCode must be checked to determine whether any errors - * occurred during header parsing. - * - * @param lis - * the nsIUnicharLineInputStream from which to parse HTTP headers - */ - _parseHeaders: function(lis) - { - var headers = this._headers; - var lastName, lastVal; - - var line = {}; - while (true) - { - NS_ASSERT((lastVal === undefined && lastName === undefined) || - (lastVal !== undefined && lastName !== undefined), - lastName === undefined ? - "lastVal without lastName? lastVal: '" + lastVal + "'" : - "lastName without lastVal? lastName: '" + lastName + "'"); - - lis.readLine(line); - var lineText = line.value; - var firstChar = lineText.charAt(0); - - // blank line means end of headers - if (lineText == "") - { - // we're finished with the previous header - if (lastName) - { - try - { - headers.setHeader(lastName, lastVal, true); - } - catch (e) - { - dumpn("*** e == " + e); - this.errorCode = 400; - return; - } - } - else - { - // no headers in request -- valid for HTTP/1.0 requests - } - - // either way, we're done processing headers - break; - } - else if (firstChar == " " || firstChar == "\t") - { - // multi-line header if we've seen a header line - if (!lastName) - { - // we don't have a header to continue! - this.errorCode = 400; - return; - } - - // append this line's text to the value; starts with SP/HT, so no need - // for separating whitespace - lastVal += lineText; - } - else - { - // we have a new header, so set the old one (if one existed) - if (lastName) - { - try - { - headers.setHeader(lastName, lastVal, true); - } - catch (e) - { - dumpn("*** e == " + e); - this.errorCode = 400; - return; - } - } - - var colon = lineText.indexOf(":"); // first colon must be splitter - if (colon < 1) - { - // no colon or missing header field-name - this.errorCode = 400; - return; - } - - // set header name, value (to be set in the next loop, usually) - lastName = lineText.substring(0, colon); - lastVal = lineText.substring(colon + 1); - } // empty, continuation, start of header - } // while (true) + if (!this._bag) + this._bag = new WritablePropertyBag(); } }; @@ -2826,9 +3289,7 @@ function server(port, basePath) srv.registerDirectory("/", lp); srv.start(port); - var thread = Cc["@mozilla.org/thread-manager;1"] - .getService() - .currentThread; + var thread = gThreadManager.currentThread; while (!srv.isStopped()) thread.processNextEvent(true); diff --git a/netwerk/test/httpserver/nsIHttpServer.idl b/netwerk/test/httpserver/nsIHttpServer.idl index 54a0f636810b..4b14c10abdb4 100644 --- a/netwerk/test/httpserver/nsIHttpServer.idl +++ b/netwerk/test/httpserver/nsIHttpServer.idl @@ -272,13 +272,13 @@ interface nsIHttpRequestMetadata : nsIPropertyBag boolean hasHeader(in string fieldName); /** - * An nsISimpleEnumerator over the names of the headers in this request. The - * header field names in the enumerator may not necessarily have the same case - * as they do in the request itself. + * An nsISimpleEnumerator of nsISupportsStrings over the names of the headers + * in this request. The header field names in the enumerator may not + * necessarily have the same case as they do in the request itself. */ readonly attribute nsISimpleEnumerator headers; - // XXX should expose body of request here! + // XXX expose request body here! }; diff --git a/netwerk/test/httpserver/test/head_utils.js b/netwerk/test/httpserver/test/head_utils.js index 2b5d1fb8c009..16ce16f5186d 100644 --- a/netwerk/test/httpserver/test/head_utils.js +++ b/netwerk/test/httpserver/test/head_utils.js @@ -41,11 +41,6 @@ do_import_script("netwerk/test/httpserver/httpd.js"); // if these tests fail, we'll want the debug output DEBUG = true; -// XPCOM constructor shorthands -const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", - "nsIBinaryInputStream", - "setInputStream"); - /** * Constructs a new nsHttpServer instance. This function is intended to diff --git a/netwerk/test/jarlist.dat b/netwerk/test/jarlist.dat index f113330e0365..15f134587e98 100644 --- a/netwerk/test/jarlist.dat +++ b/netwerk/test/jarlist.dat @@ -149,7 +149,6 @@ jar:resource:///chrome/en-US.jar!/locale/en-US/communicator/permissions/imageCon jar:resource:///chrome/en-US.jar!/locale/en-US/wallet/walletContextOverlay.dtd jar:resource:///chrome/en-US.jar!/locale/en-US/communicator/securityOverlay.dtd jar:resource:///chrome/en-US.jar!/locale/en-US/communicator/bookmarks/bookmarksOverlay.dtd -jar:resource:///chrome/en-US.jar!/locale/en-US/global/tabbrowser.dtd jar:resource:///chrome/en-US.jar!/locale/en-US/global/textcontext.dtd jar:resource:///chrome/comm.jar!/content/navigator/navigator.xul jar:resource:///chrome/comm.jar!/content/wallet/walletNavigatorOverlay.xul @@ -185,7 +184,6 @@ jar:resource:///chrome/toolkit.jar!/content/global/bindings/popup.xml jar:resource:///chrome/toolkit.jar!/content/global/bindings/toolbar.xml jar:resource:///chrome/toolkit.jar!/content/global/bindings/splitter.xml jar:resource:///chrome/modern.jar!/skin/modern/communicator/sidebar/sidebarBindings.xml -jar:resource:///chrome/toolkit.jar!/content/global/bindings/tabbrowser.xml jar:resource:///chrome/toolkit.jar!/content/global/bindings/toolbarbutton.xml jar:resource:///chrome/toolkit.jar!/content/global/bindings/text.xml jar:resource:///chrome/toolkit.jar!/content/global/bindings/progressmeter.xml diff --git a/netwerk/test/unit/test_data_protocol.js b/netwerk/test/unit/test_data_protocol.js index 7481a0875544..cc19b434fac4 100644 --- a/netwerk/test/unit/test_data_protocol.js +++ b/netwerk/test/unit/test_data_protocol.js @@ -4,10 +4,18 @@ const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; +// The behaviour wrt spaces is: +// - Textual content keeps all spaces +// - Other content strips unescaped spaces +// - Base64 content strips escaped and unescaped spaces var urls = [ - ["data:,foo", "foo"], - ["data:text/plain,foo%00 bar", "foo\x00 bar"], - ["data:text/plain;base64,Zm9 vI%20GJ%0Dhc%0Ag==", "foo bar"] + ["data:,foo", "text/plain", "foo"], + ["data:application/octet-stream,foo bar", "application/octet-stream", "foobar"], + ["data:application/octet-stream,foo%20bar", "application/octet-stream", "foo bar"], + ["data:application/xhtml+xml,foo bar", "application/xhtml+xml", "foo bar"], + ["data:application/xhtml+xml,foo%20bar", "application/xhtml+xml", "foo bar"], + ["data:text/plain,foo%00 bar", "text/plain", "foo\x00 bar"], + ["data:text/plain;base64,Zm9 vI%20GJ%0Dhc%0Ag==", "text/plain", "foo bar"] ]; function run_next_test() { @@ -93,21 +101,21 @@ Listener.prototype = { if (this._contentLen != -1 && this._buffer.length != this._contentLen) do_throw("did not read nsIChannel.contentLength number of bytes!"); - this._closure(this._buffer, this._closurectx); + this._closure(request, this._buffer, this._closurectx); } }; function run_test() { dump("*** run_test\n"); - function on_read_complete(data, idx) { + function on_read_complete(request, data, idx) { dump("*** run_test.on_read_complete\n"); - if (chan.contentType != "text/plain") - do_throw("Type mismatch! Is <" + chan.contentType + ">, should be text/plain") + if (request.nsIChannel.contentType != urls[idx][1]) + do_throw("Type mismatch! Is <" + chan.contentType + ">, should be <" + urls[idx][1] + ">"); /* read completed successfully. now compare the data. */ - if (data != urls[idx][1]) + if (data != urls[idx][2]) do_throw("Stream contents do not match with direct read!"); do_test_finished(); } diff --git a/netwerk/test/unit/test_reopen.js b/netwerk/test/unit/test_reopen.js index 0ebb3752281a..b05cc3bc2e96 100644 --- a/netwerk/test/unit/test_reopen.js +++ b/netwerk/test/unit/test_reopen.js @@ -21,10 +21,6 @@ var test_array = [ // Utility functions -var BinaryInputStream = - Components.Constructor("@mozilla.org/binaryinputstream;1", - "nsIBinaryInputStream", "setInputStream"); - function makeChan(url) { var ios = Cc["@mozilla.org/network/io-service;1"] .getService(Ci.nsIIOService); diff --git a/security/manager/boot/src/nsSecureBrowserUIImpl.cpp b/security/manager/boot/src/nsSecureBrowserUIImpl.cpp index 0801f8002a6a..4dce292e2bf0 100644 --- a/security/manager/boot/src/nsSecureBrowserUIImpl.cpp +++ b/security/manager/boot/src/nsSecureBrowserUIImpl.cpp @@ -65,12 +65,14 @@ #include "nsPIDOMWindow.h" #include "nsIContent.h" #include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" #include "nsIChannel.h" #include "nsIHttpChannel.h" #include "nsIFileChannel.h" #include "nsIWyciwygChannel.h" #include "nsIFTPChannel.h" #include "nsITransportSecurityInfo.h" +#include "nsIIdentityInfo.h" #include "nsIURI.h" #include "nsISecurityEventSink.h" #include "nsIPrompt.h" @@ -139,6 +141,7 @@ nsSecureBrowserUIImpl::nsSecureBrowserUIImpl() { mTransferringRequests.ops = nsnull; mNewToplevelSecurityState = STATE_IS_INSECURE; + mNewToplevelIsEV = PR_FALSE; mNewToplevelSecurityStateKnown = PR_TRUE; ResetStateTracking(); @@ -429,9 +432,10 @@ nsSecureBrowserUIImpl::EvaluateAndUpdateSecurityState(nsIRequest *aRequest) { nsCOMPtr channel(do_QueryInterface(aRequest)); - if (!channel) { - mNewToplevelSecurityState = nsIWebProgressListener::STATE_IS_INSECURE; - } else { + mNewToplevelSecurityState = nsIWebProgressListener::STATE_IS_INSECURE; + mNewToplevelIsEV = PR_FALSE; + + if (channel) { mNewToplevelSecurityState = GetSecurityStateFromChannel(channel); PR_LOG(gSecureDocLog, PR_LOG_DEBUG, @@ -452,6 +456,13 @@ nsSecureBrowserUIImpl::EvaluateAndUpdateSecurityState(nsIRequest *aRequest) if (secInfo) { secInfo->GetShortSecurityDescription(getter_Copies(mInfoTooltip)); } + + nsCOMPtr idinfo = do_QueryInterface(info); + if (idinfo) { + PRBool aTemp; + if (NS_SUCCEEDED(idinfo->GetIsExtendedValidation(&aTemp))) + mNewToplevelIsEV = aTemp; + } } } @@ -1139,6 +1150,9 @@ nsresult nsSecureBrowserUIImpl::UpdateSecurityState(nsIRequest* aRequest) ("SecureUI:%p: UpdateSecurityState: calling OnSecurityChange\n", this )); + if (mNewToplevelIsEV) + newState |= nsIWebProgressListener::STATE_IDENTITY_EV_TOPLEVEL; + mToplevelEventSink->OnSecurityChange(aRequest, newState); } else diff --git a/security/manager/boot/src/nsSecureBrowserUIImpl.h b/security/manager/boot/src/nsSecureBrowserUIImpl.h index 67f571cc06dd..8f81d0de8ba7 100644 --- a/security/manager/boot/src/nsSecureBrowserUIImpl.h +++ b/security/manager/boot/src/nsSecureBrowserUIImpl.h @@ -110,6 +110,7 @@ protected: void ResetStateTracking(); PRUint32 mNewToplevelSecurityState; + PRPackedBool mNewToplevelIsEV; PRPackedBool mNewToplevelSecurityStateKnown; PRPackedBool mIsViewSource; diff --git a/security/manager/ssl/public/Makefile.in b/security/manager/ssl/public/Makefile.in index 9db7f4253277..fcd4503ac466 100644 --- a/security/manager/ssl/public/Makefile.in +++ b/security/manager/ssl/public/Makefile.in @@ -57,6 +57,7 @@ SDK_XPIDLSRCS = \ nsIX509Cert.idl \ nsIX509CertDB.idl \ nsIX509CertValidity.idl \ + nsIIdentityInfo.idl \ $(NULL) XPIDLSRCS = \ diff --git a/security/manager/ssl/public/nsIIdentityInfo.idl b/security/manager/ssl/public/nsIIdentityInfo.idl new file mode 100644 index 000000000000..14a2f16dd656 --- /dev/null +++ b/security/manager/ssl/public/nsIIdentityInfo.idl @@ -0,0 +1,44 @@ +/* ***** 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 mozilla.org code. + * + * The Initial Developer of the Original Code is Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Kai Engert + * + * 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 ***** */ + +#include "nsISupports.idl" + +[scriptable, uuid(e72546a4-9b57-4766-a3aa-c05cbc7d8156)] +interface nsIIdentityInfo : nsISupports +{ + readonly attribute boolean isExtendedValidation; +}; + diff --git a/security/manager/ssl/src/Makefile.in b/security/manager/ssl/src/Makefile.in index a21482346905..9fc4691b5c9c 100644 --- a/security/manager/ssl/src/Makefile.in +++ b/security/manager/ssl/src/Makefile.in @@ -95,6 +95,7 @@ CPPSRCS = \ nsSmartCardEvent.cpp \ nsStreamCipher.cpp \ nsKeyModule.cpp \ + nsIdentityChecking.cpp \ $(NULL) ifdef MOZ_XUL diff --git a/security/manager/ssl/src/nsIdentityChecking.cpp b/security/manager/ssl/src/nsIdentityChecking.cpp new file mode 100644 index 000000000000..90cf39de6ef1 --- /dev/null +++ b/security/manager/ssl/src/nsIdentityChecking.cpp @@ -0,0 +1,11 @@ +#include "nsNSSIOLayer.h" + +NS_IMETHODIMP +nsNSSSocketInfo::GetIsExtendedValidation(PRBool* aIsEV) +{ + NS_ENSURE_ARG(aIsEV); + *aIsEV = PR_FALSE; + + return NS_OK; +} + diff --git a/security/manager/ssl/src/nsNSSCallbacks.cpp b/security/manager/ssl/src/nsNSSCallbacks.cpp index e0292afb3d89..522c2cce5b14 100644 --- a/security/manager/ssl/src/nsNSSCallbacks.cpp +++ b/security/manager/ssl/src/nsNSSCallbacks.cpp @@ -843,8 +843,13 @@ void PR_CALLBACK HandshakeCallback(PRFileDesc* fd, void* client_data) { CERTCertificate *serverCert = SSL_PeerCertificate(fd); if (serverCert) { - status->mServerCert = new nsNSSCertificate(serverCert); + nsRefPtr nssc = new nsNSSCertificate(serverCert); CERT_DestroyCertificate(serverCert); + serverCert = nsnull; + infoObject->SetCert(nssc); + if (!status->mServerCert) { + status->mServerCert = nssc; + } } status->mKeyLength = keyLength; diff --git a/security/manager/ssl/src/nsNSSCertificate.cpp b/security/manager/ssl/src/nsNSSCertificate.cpp index 093ce472fe59..e7311699e56d 100644 --- a/security/manager/ssl/src/nsNSSCertificate.cpp +++ b/security/manager/ssl/src/nsNSSCertificate.cpp @@ -1,3 +1,4 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -68,6 +69,9 @@ #include "nsUnicharUtils.h" #include "nsThreadUtils.h" #include "nsCertVerificationThread.h" +#include "nsIObjectOutputStream.h" +#include "nsIObjectInputStream.h" +#include "nsIProgrammingLanguage.h" #include "nspr.h" extern "C" { @@ -104,25 +108,38 @@ NS_IMPL_THREADSAFE_ISUPPORTS4(nsNSSCertificate, nsIX509Cert, nsNSSCertificate* nsNSSCertificate::ConstructFromDER(char *certDER, int derLen) +{ + nsNSSCertificate* newObject = new nsNSSCertificate(); + if (!newObject->InitFromDER(certDER, derLen)) { + delete newObject; + newObject = nsnull; + } + + return newObject; +} + +PRBool +nsNSSCertificate::InitFromDER(char *certDER, int derLen) { nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return PR_FALSE; if (!certDER || !derLen) - return nsnull; + return PR_FALSE; CERTCertificate *aCert = CERT_DecodeCertFromPackage(certDER, derLen); if (!aCert) - return nsnull; + return PR_FALSE; if(aCert->dbhandle == nsnull) { aCert->dbhandle = CERT_GetDefaultCertDB(); } - nsNSSCertificate *newObject = new nsNSSCertificate(aCert); - CERT_DestroyCertificate(aCert); - return newObject; + mCert = aCert; + return PR_TRUE; } nsNSSCertificate::nsNSSCertificate(CERTCertificate *cert) : @@ -138,6 +155,13 @@ nsNSSCertificate::nsNSSCertificate(CERTCertificate *cert) : mCert = CERT_DupCertificate(cert); } +nsNSSCertificate::nsNSSCertificate() : + mCert(nsnull), + mPermDelete(PR_FALSE), + mCertType(CERT_TYPE_NOT_YET_INITIALIZED) +{ +} + nsNSSCertificate::~nsNSSCertificate() { nsNSSShutDownPreventionLock locker; @@ -1470,3 +1494,100 @@ nsNSSCertListEnumerator::GetNext(nsISupports **_retval) CERT_RemoveCertListNode(node); return NS_OK; } + +NS_IMETHODIMP +nsNSSCertificate::Write(nsIObjectOutputStream* aStream) +{ + NS_ENSURE_STATE(mCert); + nsresult rv = aStream->Write32(mCert->derCert.len); + if (NS_FAILED(rv)) { + return rv; + } + + return aStream->WriteByteArray(mCert->derCert.data, mCert->derCert.len); +} + +NS_IMETHODIMP +nsNSSCertificate::Read(nsIObjectInputStream* aStream) +{ + NS_ENSURE_STATE(!mCert); + + PRUint32 len; + nsresult rv = aStream->Read32(&len); + if (NS_FAILED(rv)) { + return rv; + } + + nsXPIDLCString str; + rv = aStream->ReadBytes(len, getter_Copies(str)); + if (NS_FAILED(rv)) { + return rv; + } + + if (!InitFromDER(const_cast(str.get()), len)) { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsNSSCertificate::GetInterfaces(PRUint32 *count, nsIID * **array) +{ + *count = 0; + *array = nsnull; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSCertificate::GetHelperForLanguage(PRUint32 language, nsISupports **_retval) +{ + *_retval = nsnull; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSCertificate::GetContractID(char * *aContractID) +{ + *aContractID = nsnull; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSCertificate::GetClassDescription(char * *aClassDescription) +{ + *aClassDescription = nsnull; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSCertificate::GetClassID(nsCID * *aClassID) +{ + *aClassID = (nsCID*) nsMemory::Alloc(sizeof(nsCID)); + if (!*aClassID) + return NS_ERROR_OUT_OF_MEMORY; + return GetClassIDNoAlloc(*aClassID); +} + +NS_IMETHODIMP +nsNSSCertificate::GetImplementationLanguage(PRUint32 *aImplementationLanguage) +{ + *aImplementationLanguage = nsIProgrammingLanguage::CPLUSPLUS; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSCertificate::GetFlags(PRUint32 *aFlags) +{ + *aFlags = nsIClassInfo::THREADSAFE; + return NS_OK; +} + +static NS_DEFINE_CID(kNSSCertificateCID, NS_X509CERT_CID); + +NS_IMETHODIMP +nsNSSCertificate::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) +{ + *aClassIDNoAlloc = kNSSCertificateCID; + return NS_OK; +} diff --git a/security/manager/ssl/src/nsNSSCertificate.h b/security/manager/ssl/src/nsNSSCertificate.h index 0b89911005d3..88d5cdf8103a 100644 --- a/security/manager/ssl/src/nsNSSCertificate.h +++ b/security/manager/ssl/src/nsNSSCertificate.h @@ -1,3 +1,4 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -49,6 +50,8 @@ #include "nsISMimeCert.h" #include "nsNSSShutDown.h" #include "nsISimpleEnumerator.h" +#include "nsISerializable.h" +#include "nsIClassInfo.h" #include "nsNSSCertHeader.h" @@ -60,6 +63,8 @@ class nsNSSCertificate : public nsIX509Cert, public nsIX509Cert2, public nsIX509Cert3, public nsISMimeCert, + public nsISerializable, + public nsIClassInfo, public nsNSSShutDownObject { public: @@ -68,8 +73,11 @@ public: NS_DECL_NSIX509CERT2 NS_DECL_NSIX509CERT3 NS_DECL_NSISMIMECERT + NS_DECL_NSISERIALIZABLE + NS_DECL_NSICLASSINFO nsNSSCertificate(CERTCertificate *cert); + nsNSSCertificate(); /* from a request? */ virtual ~nsNSSCertificate(); nsresult FormatUIStrings(const nsAutoString &nickname, nsAutoString &nickWithSerial, nsAutoString &details); @@ -88,6 +96,7 @@ private: nsresult GetSortableDate(PRTime aTime, nsAString &_aSortableDate); virtual void virtualDestroyNSSReference(); void destructorSafeDestroyNSSReference(); + PRBool InitFromDER(char* certDER, int derLen); // return false on failure }; class nsNSSCertList: public nsIX509CertList @@ -127,7 +136,11 @@ private: (dest)[2] = (((src) >> 8) & 0xff); \ (dest)[3] = ((src) & 0xff); - - +#define NS_X509CERT_CID { /* 660a3226-915c-4ffb-bb20-8985a632df05 */ \ + 0x660a3226, \ + 0x915c, \ + 0x4ffb, \ + { 0xbb, 0x20, 0x89, 0x85, 0xa6, 0x32, 0xdf, 0x05 } \ + } #endif /* _NS_NSSCERTIFICATE_H_ */ diff --git a/security/manager/ssl/src/nsNSSIOLayer.cpp b/security/manager/ssl/src/nsNSSIOLayer.cpp index 4772b2ba2f69..12cbb40b097f 100644 --- a/security/manager/ssl/src/nsNSSIOLayer.cpp +++ b/security/manager/ssl/src/nsNSSIOLayer.cpp @@ -231,11 +231,12 @@ void nsNSSSocketInfo::destructorSafeDestroyNSSReference() } } -NS_IMPL_THREADSAFE_ISUPPORTS4(nsNSSSocketInfo, +NS_IMPL_THREADSAFE_ISUPPORTS5(nsNSSSocketInfo, nsITransportSecurityInfo, nsISSLSocketControl, nsIInterfaceRequestor, - nsISSLStatusProvider) + nsISSLStatusProvider, + nsIIdentityInfo) nsresult nsNSSSocketInfo::GetHandshakePending(PRBool *aHandshakePending) @@ -504,6 +505,23 @@ nsresult nsNSSSocketInfo::SetFileDescPtr(PRFileDesc* aFilePtr) return NS_OK; } +nsresult nsNSSSocketInfo::GetCert(nsNSSCertificate** _result) +{ + NS_ENSURE_ARG_POINTER(_result); + + *_result = mCert; + NS_IF_ADDREF(*_result); + + return NS_OK; +} + +nsresult nsNSSSocketInfo::SetCert(nsNSSCertificate *aCert) +{ + mCert = aCert; + + return NS_OK; +} + nsresult nsNSSSocketInfo::GetSSLStatus(nsISupports** _result) { NS_ENSURE_ARG_POINTER(_result); diff --git a/security/manager/ssl/src/nsNSSIOLayer.h b/security/manager/ssl/src/nsNSSIOLayer.h index 4d1fcaea0afa..228416b9c34f 100644 --- a/security/manager/ssl/src/nsNSSIOLayer.h +++ b/security/manager/ssl/src/nsNSSIOLayer.h @@ -51,8 +51,11 @@ #include "nsISSLSocketControl.h" #include "nsISSLStatus.h" #include "nsISSLStatusProvider.h" +#include "nsIIdentityInfo.h" #include "nsXPIDLString.h" #include "nsNSSShutDown.h" +#include "nsAutoPtr.h" +#include "nsNSSCertificate.h" class nsIChannel; class nsSSLThread; @@ -123,6 +126,7 @@ class nsNSSSocketInfo : public nsITransportSecurityInfo, public nsISSLSocketControl, public nsIInterfaceRequestor, public nsISSLStatusProvider, + public nsIIdentityInfo, public nsNSSShutDownObject, public nsOnPK11LogoutCancelObject { @@ -135,6 +139,7 @@ public: NS_DECL_NSISSLSOCKETCONTROL NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSISSLSTATUSPROVIDER + NS_DECL_NSIIDENTITYINFO nsresult SetSecurityState(PRUint32 aState); nsresult SetShortSecurityDescription(const PRUnichar *aText); @@ -155,6 +160,9 @@ public: nsresult GetPort(PRInt32 *aPort); nsresult SetPort(PRInt32 aPort); + nsresult GetCert(nsNSSCertificate** _result); + nsresult SetCert(nsNSSCertificate *aCert); + void SetCanceled(PRBool aCanceled); PRBool GetCanceled(); @@ -187,6 +195,7 @@ public: protected: nsCOMPtr mCallbacks; PRFileDesc* mFd; + nsRefPtr mCert; enum { blocking_state_unknown, is_nonblocking_socket, is_blocking_socket } mBlockingState; diff --git a/security/manager/ssl/src/nsNSSModule.cpp b/security/manager/ssl/src/nsNSSModule.cpp index cf45dfbdc83a..fcf443401348 100644 --- a/security/manager/ssl/src/nsNSSModule.cpp +++ b/security/manager/ssl/src/nsNSSModule.cpp @@ -170,6 +170,7 @@ NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsSecretDecoderRing) NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsPK11TokenDB) NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsPKCS11ModuleDB) NS_NSS_GENERIC_FACTORY_CONSTRUCTOR_INIT(PR_FALSE, PSMContentListener, init) +NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsNSSCertificate) NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsNSSCertificateDB) NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsNSSCertCache) #ifdef MOZ_XUL @@ -298,6 +299,13 @@ static const nsModuleComponentInfo components[] = PSMContentListenerConstructor }, + { + "X509 Certificate", + NS_X509CERT_CID, + nsnull, + nsNSSCertificateConstructor + }, + { "X509 Certificate Database", NS_X509CERTDB_CID, diff --git a/testing/extensions/community/chrome/content/accountcreate.js b/testing/extensions/community/chrome/content/accountcreate.js new file mode 100644 index 000000000000..126036e8296e --- /dev/null +++ b/testing/extensions/community/chrome/content/accountcreate.js @@ -0,0 +1,108 @@ + + +var accountCreate = { + oldPassword: null, //TODO: is this secure? + oldUsername: null, + updateFunction : null, + loadSetup : function() { + document.getElementById('qa-setup-createaccount-iframe').src = + litmus.baseURL+'extension.cgi?createAccount=1'; + accountCreate.updateFunction = window.arguments[0]; + + accountCreate.oldPassword = qaPref.litmus.getPassword(); + accountCreate.oldUsername = qaPref.litmus.getUsername(); + }, + validateAccount : function() { + // we're creating a new account, so get the uname and passwd + // from the account created page: + var page = document.getElementById('qa-setup-createaccount-iframe'). + contentDocument; + if (!page) { + alert("create account page is missing"); + return false; + } + if (page.wrappedJSObject == null) + page.wrappedJSObject = page; + if (page.forms[0] && page.forms[0].wrappedJSObject == null) + page.forms[0].wrappedJSObject = page.forms[0]; + + if (page.location == litmus.baseURL+'extension.cgi?createAccount=1' + && qaSetup.didSubmitForm==0) { + page.forms[0].wrappedJSObject.submit(); + qaSetup.didSubmitForm = 1; + setTimeout("qaSetup.validateAccount()", 5000); + return false; + } + if (qaSetup.didSubmitForm == 1 && ! page.forms || + ! page.forms[0].wrappedJSObject || + ! page.forms[0].wrappedJSObject.email && + ! page.forms[0].wrappedJSObject.email.value) + {qaSetup.didSubmitForm = 2; + setTimeout("qaSetup.validateAccount()", 4000); + return false;} + var e = ''; + var p = ''; + if (page.forms && page.forms[0].wrappedJSObject && + page.forms[0].wrappedJSObject.email && + page.forms[0].wrappedJSObject.email.value) + { e=page.forms[0].wrappedJSObject.email.value } + if (page.forms && page.forms[0].wrappedJSObject && + page.forms[0].wrappedJSObject.password && + page.forms[0].wrappedJSObject.password.value) + { p=page.forms[0].wrappedJSObject.password.value } + // e is account, p is password + + var uname = e; + var passwd = p; + + + if (e == '' || p == '') { + alert("No username or password has been registered."); + return false; //we need better error handling. + } + qaPref.litmus.setPassword(uname, passwd); + accountCreate.updateFunction(); + + // TODO: ideally we would validate against litmus, but... + return true; + }, + + handleCancel : function() { + qaPref.litmus.setPassword(oldUsername, oldPassword); + } +} diff --git a/testing/extensions/community/chrome/content/accountcreate.xul b/testing/extensions/community/chrome/content/accountcreate.xul new file mode 100644 index 000000000000..abfe41ce7ff0 --- /dev/null +++ b/testing/extensions/community/chrome/content/accountcreate.xul @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + +Login Manager test: multiple login autocomplete +

+ + +
+ +
+ + + + + +
+ +
+ +
+
+
+ + + diff --git a/toolkit/components/passwordmgr/test/unit/test_storage_legacy_1.js b/toolkit/components/passwordmgr/test/unit/test_storage_legacy_1.js index 51ce77125102..88a3e2021805 100644 --- a/toolkit/components/passwordmgr/test/unit/test_storage_legacy_1.js +++ b/toolkit/components/passwordmgr/test/unit/test_storage_legacy_1.js @@ -112,6 +112,14 @@ testdesc = "Initialize with signons-05.txt (0 disabled, 1 login)"; LoginTest.initStorage(storage, INDIR, "signons-05.txt"); LoginTest.checkStorageData(storage, [], [testuser1]); +// counting logins matching host +do_check_eq(1, storage.countLogins("http://dummyhost.mozilla.org", "", null)); +// counting logins matching host (login has blank actionURL) +do_check_eq(1, storage.countLogins("http://dummyhost.mozilla.org", "foo", null)); +// counting logins (don't match form login for HTTP search) +do_check_eq(0, storage.countLogins("http://dummyhost.mozilla.org", null, "")); +// counting logins (don't match a bogus hostname) +do_check_eq(0, storage.countLogins("blah", "", "")); /* ========== 9 ========== */ @@ -128,6 +136,14 @@ testdesc = "Initialize with signons-07.txt (0 disabled, 2 logins on same host)"; LoginTest.initStorage(storage, INDIR, "signons-07.txt"); LoginTest.checkStorageData(storage, [], [testuser1, testuser2]); +// counting logins matching host +do_check_eq(2, storage.countLogins("http://dummyhost.mozilla.org", "", null)); +// counting logins matching host (login has blank actionURL) +do_check_eq(2, storage.countLogins("http://dummyhost.mozilla.org", "foo", null)); +// counting logins (don't match form login for HTTP search) +do_check_eq(0, storage.countLogins("http://dummyhost.mozilla.org", null, "")); +// counting logins (don't match a bogus hostname) +do_check_eq(0, storage.countLogins("blah", "", "")); /* ========== 11 ========== */ @@ -150,6 +166,10 @@ for (i = 1; i <= 500; i++) { } LoginTest.checkStorageData(storage, disabledHosts, logins); +// counting all logins for dummyhost +do_check_eq(500, storage.countLogins("http://dummyhost.site.org", "", "")); +// counting all logins for dummyhost-1 +do_check_eq(1, storage.countLogins("http://dummyhost-1.site.org", "", "")); } catch (e) { throw "FAILED in test #" + testnum + " -- " + testdesc + ": " + e; diff --git a/toolkit/components/passwordmgr/test/unit/test_storage_legacy_2.js b/toolkit/components/passwordmgr/test/unit/test_storage_legacy_2.js index 9ff4d6ae79e6..bd9b7433dde8 100644 --- a/toolkit/components/passwordmgr/test/unit/test_storage_legacy_2.js +++ b/toolkit/components/passwordmgr/test/unit/test_storage_legacy_2.js @@ -198,6 +198,14 @@ LoginTest.initStorage(storage, OUTDIR, "output-05.txt", null, null); testdesc = "Verify output-05.txt"; LoginTest.checkStorageData(storage, [], [dummyuser1, dummyuser2, dummyuser3]); +// count dummyhost2 logins +do_check_eq(2, storage.countLogins("http://dummyhost2.mozilla.org", "", "")); +// count dummyhost logins +do_check_eq(1, storage.countLogins("http://dummyhost.mozilla.org", "", "")); + +// count dummyhost2 logins w/ specific formSubmitURL +do_check_eq(2, storage.countLogins("http://dummyhost2.mozilla.org", "http://cgi.site.com", "")); + /* ========== 10 ========== */ testnum++; diff --git a/toolkit/components/places/src/nsNavHistory.cpp b/toolkit/components/places/src/nsNavHistory.cpp index 8303402db3ff..425842550655 100644 --- a/toolkit/components/places/src/nsNavHistory.cpp +++ b/toolkit/components/places/src/nsNavHistory.cpp @@ -221,8 +221,8 @@ const PRInt32 nsNavHistory::kGetInfoIndex_ItemLastModified = 10; const PRInt32 nsNavHistory::kAutoCompleteIndex_URL = 0; const PRInt32 nsNavHistory::kAutoCompleteIndex_Title = 1; -const PRInt32 nsNavHistory::kAutoCompleteIndex_VisitCount = 2; -const PRInt32 nsNavHistory::kAutoCompleteIndex_Typed = 3; +const PRInt32 nsNavHistory::kAutoCompleteIndex_FaviconURL = 2; +const PRInt32 nsNavHistory::kAutoCompleteIndex_ItemId = 3; static nsDataHashtable* gTldTypes; static const char* gQuitApplicationMessage = "quit-application"; @@ -258,6 +258,11 @@ nsNavHistory::nsNavHistory() : mNowValid(PR_FALSE), nsNavHistory::~nsNavHistory() { + if (mAutoCompleteTimer) { + mAutoCompleteTimer->Cancel(); + mAutoCompleteTimer = nsnull; + } + // remove the static reference to the service. Check to make sure its us // in case somebody creates an extra instance of the service. NS_ASSERTION(gHistoryService == this, "YOU CREATED 2 COPIES OF THE HISTORY SERVICE."); @@ -762,18 +767,6 @@ nsNavHistory::InitStatements() getter_AddRefs(mDBGetIdPageInfoFull)); NS_ENSURE_SUCCESS(rv, rv); - // mDBFullAutoComplete - rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( - "SELECT h.url, h.title, h.visit_count, h.typed " - "FROM moz_places h " - "JOIN moz_historyvisits v ON h.id = v.place_id " - "WHERE h.hidden <> 1 " - "GROUP BY h.id " - "ORDER BY h.visit_count " - "LIMIT ?1"), - getter_AddRefs(mDBFullAutoComplete)); - NS_ENSURE_SUCCESS(rv, rv); - // mDBRecentVisitOfURL rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT v.id, v.session " diff --git a/toolkit/components/places/src/nsNavHistory.h b/toolkit/components/places/src/nsNavHistory.h index 2b75fb479d9b..5dce3e50c3b3 100644 --- a/toolkit/components/places/src/nsNavHistory.h +++ b/toolkit/components/places/src/nsNavHistory.h @@ -362,11 +362,6 @@ protected: nsCOMPtr mDBGetURLPageInfoFull; // kGetInfoIndex_* results nsCOMPtr mDBGetIdPageInfo; // kGetInfoIndex_* results nsCOMPtr mDBGetIdPageInfoFull; // kGetInfoIndex_* results - nsCOMPtr mDBFullAutoComplete; // kAutoCompleteIndex_* results, 1 arg (max # results) - static const PRInt32 kAutoCompleteIndex_URL; - static const PRInt32 kAutoCompleteIndex_Title; - static const PRInt32 kAutoCompleteIndex_VisitCount; - static const PRInt32 kAutoCompleteIndex_Typed; nsCOMPtr mDBRecentVisitOfURL; // converts URL into most recent visit ID/session ID nsCOMPtr mDBInsertVisit; // used by AddVisit @@ -560,49 +555,31 @@ protected: // // AutoComplete stuff // - struct AutoCompletePrefix - { - AutoCompletePrefix(const nsAString& aPrefix, PRBool aSecondLevel) : - prefix(aPrefix), secondLevel(aSecondLevel) {} - - // The prefix, for example, "http://" or "https://www." - nsString prefix; - - // Set when this prefix contains a spec AND a host. For example, - // "http://www." is a second level prefix, but "http://" is not. This - // flag is used to exclude matches. For example, if I type "http://w" - // I probably want it to autocomplete to sites beginning with w and - // NOT every "www" site I've ever visited. - PRBool secondLevel; - }; - nsTArray mAutoCompletePrefixes; - - nsCOMPtr mDBAutoCompleteQuery; + static const PRInt32 kAutoCompleteIndex_URL; + static const PRInt32 kAutoCompleteIndex_Title; + static const PRInt32 kAutoCompleteIndex_FaviconURL; + static const PRInt32 kAutoCompleteIndex_ItemId; + nsCOMPtr mDBAutoCompleteQuery; // kAutoCompleteIndex_* results nsresult InitAutoComplete(); nsresult CreateAutoCompleteQuery(); - PRInt32 mAutoCompleteMaxCount; - PRInt32 mExpireDays; PRBool mAutoCompleteOnlyTyped; + nsCOMPtr mAutoCompleteTimer; - // Used to describe what prefixes shouldn't be cut from - // history urls when doing an autocomplete url comparison. - struct AutoCompleteExclude { - // these are indices into mIgnoreSchemes and mIgnoreHostnames, or -1 - PRInt32 schemePrefix; - PRInt32 hostnamePrefix; + nsString mCurrentSearchString; + nsCOMPtr mCurrentListener; + nsCOMPtr mCurrentResult; + nsDataHashtable mCurrentResultURLs; + PRTime mCurrentChunkEndTime; + PRTime mCurrentOldestVisit; - // this is the offset of the character immediately after the prefix - PRInt32 postPrefixOffset; - }; + nsresult AutoCompleteTypedSearch(); + nsresult AutoCompleteFullHistorySearch(); - nsresult AutoCompleteTypedSearch(nsIAutoCompleteSimpleResult* result); - nsresult AutoCompleteFullHistorySearch(const nsAString& aSearchString, - nsIAutoCompleteSimpleResult* result); - nsresult AutoCompleteQueryOnePrefix(const nsString& aSearchString, - const nsTArray& aExcludePrefixes, - PRInt32 aPriorityDelta, - nsTArray* aResult); - PRInt32 AutoCompleteGetPrefixLength(const nsString& aSpec); + nsresult PerformAutoComplete(); + nsresult StartAutoCompleteTimer(PRUint32 aMilliseconds); + static void AutoCompleteTimerCallback(nsITimer* aTimer, void* aClosure); + + PRInt32 mExpireDays; // in nsNavHistoryQuery.cpp nsresult TokensToQueries(const nsTArray& aTokens, diff --git a/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp b/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp index b493c70b90de..eeddaeada236 100644 --- a/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp +++ b/toolkit/components/places/src/nsNavHistoryAutoComplete.cpp @@ -42,58 +42,11 @@ /** * Autocomplete algorithm: * - * Scoring - * ------- - * Generally ordering is by visit count. We given boosts to items that have - * been bookmarked or typed into the address bar (as opposed to clicked links). - * The penalties below for schemes and prefix matches also apply. We also - * prefer paths (URLs ending in '/') and try to have shorter ones generally - * appear first. The results are then presented in descending numeric order - * using this score. - * - * Database queries - * ---------------- - * It is tempting to do a select for "url LIKE user_input" but this is a very - * slow query since it is brute-force over all URLs in histroy. We can, - * however, do very efficient queries using < and > for strings. These - * operators use the index over the URL column. - * - * Therefore, we try to prepend any prefixes that should be considered and - * query for them individually. Therefore, we execute several very efficient - * queries, score the results, and sort it. - * - * To limit the amount of searching we do, we ask the database to order the - * results based on visit count for us and give us on the top N results. - * These results will be in approximate order of score. As long as each query - * has more results than the user is likely to see, they will not notice the - * effects of this. - * - * First see if any specs match that from the beginning - * ---------------------------------------------------- - * - If it matches the beginning of a known prefix prefix, exclude that prefix - * when querying. We would therefore exclude "http://" and "https://" if you type - * "ht". But match any other schemes that begin with "ht" (probably none). - * - * - Penalize all results. Most people don't type the scheme and don't want - * matches like this. This will make "file://" links go below - * "http://www.fido.com/". If one is typing the scheme "file:" for example, by - * the time you type the colon it won't match anything else (like "http://file:") - * and the penalty won't have any effect on the ordering (it will be applied to - * all results). - * - * Try different known prefixes - * ---------------------------- - * - Prepend each prefix, running a query. If the concatenated string is itself a - * prefix of another known prefix (ie input is "w" and we prepend "http://", it - * will be a prefix of "http://www."), select out that known prefix. In this - * case we'll query for everything starting with "http://w" except things - * starting with "http://www." - * - * - For each selected out prefix above, run a query but apply prefix match - * penalty to the results. This way you'll still get "http://www." results - * if you type "w", but they will generally be lower than "http://wookie.net/" - * For your favorite few sites with many visits, you might still get matches - * for "www" first, which is probably what you want for your favorite sites. + * The current algorithm searches history from now backwards to the oldest + * visit in chunks of time (AUTOCOMPLETE_SEARCH_CHUNK). We currently + * do SQL LIKE searches of the search term in the url and title + * within in each chunk of time. the results are ordered by visit_count + * (and then visit_date), giving us poor man's "frecency". */ #include "nsNavHistory.h" @@ -107,157 +60,24 @@ #include "mozStorageCID.h" #include "mozStorageHelper.h" #include "nsFaviconService.h" +#include "nsUnicharUtils.h" #define NS_AUTOCOMPLETESIMPLERESULT_CONTRACTID \ "@mozilla.org/autocomplete/simple-result;1" -// Size of visit count boost to give to URLs which are sites or paths -#define AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST 5 - -// Boost to give to URLs which have been typed -#define AUTOCOMPLETE_TYPED_BOOST 5 - -// Boost to give to URLs which are bookmarked -#define AUTOCOMPLETE_BOOKMARKED_BOOST 5 - -// Penalty to add to sites that match a prefix. For example, if I type "w" -// we will complte on "http://www.w..." like normal. We we will also complete -// on "http://w" which will match almost every site, but will have this penalty -// applied so they will come later. We want a pretty big penalty so that you'll -// only get "www" beating domain names that start with w for your very favorite -// sites. -#define AUTOCOMPLETE_MATCHES_PREFIX_PENALTY (-50) - -// Penalty applied to matches that don't have prefixes applied. See -// discussion above. -#define AUTOCOMPLETE_MATCHES_SCHEME_PENALTY (-20) - -// Number of results we will consider for each prefix. Each prefix lookup is -// done separately. Typically, we will only match one prefix, so this should be -// a sufficient number to give "enough" autocomplete matches per prefix. The -// total number of results that could ever be returned is this times the number -// of prefixes. This should be as small as is reasonable to make it faster. -#define AUTOCOMPLETE_MAX_PER_PREFIX 50 - -// This is the maximum results we'll return for a "typed" search (usually -// happens in response to clicking the down arrow next to the URL). +// This is the maximum results we'll return for a "typed" search +// This happens in response to clicking the down arrow next to the URL. #define AUTOCOMPLETE_MAX_PER_TYPED 100 -PRInt32 ComputeAutoCompletePriority(const nsAString& aUrl, PRInt32 aVisitCount, - PRBool aWasTyped, PRBool aIsBookmarked); -nsresult NormalizeAutocompleteInput(const nsAString& aInput, - nsString& aOutput); - -// nsIAutoCompleteSearch ******************************************************* - - -// AutoCompleteIntermediateResult/Set -// -// This class holds intermediate autocomplete results so that they can be -// sorted. This list is then handed off to a result using FillResult. The -// major reason for this is so that we can use nsArray's sorting functions, -// not use COM, yet have well-defined lifetimes for the objects. This uses -// a void array, but makes sure to delete the objects on desctruction. - -struct AutoCompleteIntermediateResult -{ - AutoCompleteIntermediateResult(const nsString& aUrl, const nsString& aTitle, - const nsString& aImage, - PRInt32 aVisitCount, PRInt32 aPriority) : - url(aUrl), title(aTitle), image(aImage), - visitCount(aVisitCount), priority(aPriority) {} - nsString url; - nsString title; - nsString image; - PRInt32 visitCount; - PRInt32 priority; -}; - - -// AutoCompleteResultComparator - -class AutoCompleteResultComparator -{ -public: - AutoCompleteResultComparator(nsNavHistory* history) : mHistory(history) {} - - PRBool Equals(const AutoCompleteIntermediateResult& a, - const AutoCompleteIntermediateResult& b) const { - // Don't need an equals, this call will be optimized out when it - // is used by nsQuickSortComparator above - return PR_FALSE; - } - PRBool LessThan(const AutoCompleteIntermediateResult& match1, - const AutoCompleteIntermediateResult& match2) const { - // we actually compare GREATER than here, since we want the array to be in - // most relevant (highest priority value) first - - // In most cases the priorities will be different and we just use them - if (match1.priority != match2.priority) - { - return match1.priority > match2.priority; - } - else - { - // secondary sorting gives priority to site names and paths (ending in a /) - PRBool isPath1 = PR_FALSE, isPath2 = PR_FALSE; - if (!match1.url.IsEmpty()) - isPath1 = (match1.url.Last() == PRUnichar('/')); - if (!match2.url.IsEmpty()) - isPath2 = (match2.url.Last() == PRUnichar('/')); - - if (isPath1 && !isPath2) return PR_FALSE; // match1->url is a website/path, match2->url isn't - if (!isPath1 && isPath2) return PR_TRUE; // match1->url isn't a website/path, match2->url is - - // find the prefixes so we can sort by the stuff after the prefixes - PRInt32 prefix1 = mHistory->AutoCompleteGetPrefixLength(match1.url); - PRInt32 prefix2 = mHistory->AutoCompleteGetPrefixLength(match2.url); - - // Compare non-prefixed urls using the current locale string compare. This will sort - // things alphabetically (ignoring common prefixes). For example, "http://www.abc.com/" - // will come before "ftp://ftp.xyz.com" - PRInt32 ret = 0; - mHistory->mCollation->CompareString( - nsICollation::kCollationCaseInSensitive, - Substring(match1.url, prefix1), Substring(match2.url, prefix2), - &ret); - if (ret != 0) - return ret > 0; - - // sort http://xyz.com before http://www.xyz.com - return prefix1 > prefix2; - } - return PR_FALSE; - } -protected: - nsNavHistory* mHistory; -}; - - // nsNavHistory::InitAutoComplete - nsresult nsNavHistory::InitAutoComplete() { nsresult rv = CreateAutoCompleteQuery(); NS_ENSURE_SUCCESS(rv, rv); - AutoCompletePrefix* ok; - - // These are the prefixes we check for implicitly. Prefixes with a - // host portion (like "www.") get their second level flag set. - ok = mAutoCompletePrefixes.AppendElement(AutoCompletePrefix(NS_LITERAL_STRING("http://"), PR_FALSE)); - NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); - ok = mAutoCompletePrefixes.AppendElement(AutoCompletePrefix(NS_LITERAL_STRING("http://www."), PR_TRUE)); - NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); - ok = mAutoCompletePrefixes.AppendElement(AutoCompletePrefix(NS_LITERAL_STRING("ftp://"), PR_FALSE)); - NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); - ok = mAutoCompletePrefixes.AppendElement(AutoCompletePrefix(NS_LITERAL_STRING("ftp://ftp."), PR_TRUE)); - NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); - ok = mAutoCompletePrefixes.AppendElement(AutoCompletePrefix(NS_LITERAL_STRING("https://"), PR_FALSE)); - NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); - ok = mAutoCompletePrefixes.AppendElement(AutoCompletePrefix(NS_LITERAL_STRING("https://www."), PR_TRUE)); - NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); + if (!mCurrentResultURLs.Init(128)) + return NS_ERROR_OUT_OF_MEMORY; return NS_OK; } @@ -271,34 +91,120 @@ nsNavHistory::InitAutoComplete() nsresult nsNavHistory::CreateAutoCompleteQuery() { - nsCString sql; - if (mAutoCompleteOnlyTyped) { - sql = NS_LITERAL_CSTRING( - "SELECT p.url, p.title, p.visit_count, p.typed, " - "(SELECT b.fk FROM moz_bookmarks b WHERE b.fk = p.id), f.url " - "FROM moz_places p " - "LEFT OUTER JOIN moz_favicons f ON p.favicon_id = f.id " - "WHERE p.url >= ?1 AND p.url < ?2 " - "AND p.typed = 1 " - "ORDER BY p.visit_count DESC " - "LIMIT "); - } else { - sql = NS_LITERAL_CSTRING( - "SELECT p.url, p.title, p.visit_count, p.typed, " - "(SELECT b.fk FROM moz_bookmarks b WHERE b.fk = p.id), f.url " - "FROM moz_places p " - "LEFT OUTER JOIN moz_favicons f ON p.favicon_id = f.id " - "WHERE p.url >= ?1 AND p.url < ?2 " - "AND (p.hidden <> 1 OR p.typed = 1) " - "ORDER BY p.visit_count DESC " - "LIMIT "); - } - sql.AppendInt(AUTOCOMPLETE_MAX_PER_PREFIX); - nsresult rv = mDBConn->CreateStatement(sql, - getter_AddRefs(mDBAutoCompleteQuery)); - return rv; + nsCString sql = NS_LITERAL_CSTRING( + "SELECT h.url, h.title, f.url, b.id " + "FROM moz_places h " + "JOIN moz_historyvisits v ON h.id = v.place_id " + "LEFT OUTER JOIN moz_bookmarks b ON b.fk = h.id " + "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id " + "WHERE v.visit_date >= ?1 AND v.visit_date <= ?2 AND h.hidden <> 1 AND " + " v.visit_type <> 0 AND v.visit_type <> 4 AND "); + + if (mAutoCompleteOnlyTyped) + sql += NS_LITERAL_CSTRING("h.typed = 1 AND "); + + sql += NS_LITERAL_CSTRING( + "(h.title LIKE ?3 ESCAPE '/' OR h.url LIKE ?3 ESCAPE '/') " + "GROUP BY h.id ORDER BY h.visit_count DESC, MAX(v.visit_date) DESC "); + + return mDBConn->CreateStatement(sql, getter_AddRefs(mDBAutoCompleteQuery)); } +// nsNavHistory::StartAutoCompleteTimer + +nsresult +nsNavHistory::StartAutoCompleteTimer(PRUint32 aMilliseconds) +{ + nsresult rv; + + if (!mAutoCompleteTimer) { + mAutoCompleteTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = mAutoCompleteTimer->InitWithFuncCallback(AutoCompleteTimerCallback, this, + aMilliseconds, + nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +static const PRInt64 USECS_PER_DAY = LL_INIT(20, 500654080); + +// search in day chunks. too big, and the UI will be unresponsive +// as we will be off searching the database. +// too short, and because of AUTOCOMPLETE_SEARCH_TIMEOUT +// results won't come back in fast enough to feel snappy. +// because we sort within chunks by visit_count (then visit_date) +// choose 4 days so that Friday's searches are in the first chunk +// and those will affect Monday's results +#define AUTOCOMPLETE_SEARCH_CHUNK (USECS_PER_DAY * 4) + +// wait this many milliseconds between searches +// too short, and the UI will be unresponsive +// as we will be off searching the database. +// too big, and results won't come back in fast enough to feel snappy. +#define AUTOCOMPLETE_SEARCH_TIMEOUT 100 + +// nsNavHistory::AutoCompleteTimerCallback + +void // static +nsNavHistory::AutoCompleteTimerCallback(nsITimer* aTimer, void* aClosure) +{ + nsNavHistory* history = static_cast(aClosure); + (void)history->PerformAutoComplete(); +} + +nsresult +nsNavHistory::PerformAutoComplete() +{ + // if there is no listener, our search has been stopped + if (!mCurrentListener) + return NS_OK; + + mCurrentResult->SetSearchString(mCurrentSearchString); + PRBool moreChunksToSearch = PR_FALSE; + + nsresult rv; + // results will be put into mCurrentResult + if (mCurrentSearchString.IsEmpty()) + rv = AutoCompleteTypedSearch(); + else { + rv = AutoCompleteFullHistorySearch(); + moreChunksToSearch = (mCurrentChunkEndTime >= mCurrentOldestVisit); + } + NS_ENSURE_SUCCESS(rv, rv); + + // Determine the result of the search + PRUint32 count; + mCurrentResult->GetMatchCount(&count); + + if (count > 0) { + mCurrentResult->SetSearchResult(moreChunksToSearch ? + nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING : + nsIAutoCompleteResult::RESULT_SUCCESS); + mCurrentResult->SetDefaultIndex(0); + } else { + mCurrentResult->SetSearchResult(moreChunksToSearch ? + nsIAutoCompleteResult::RESULT_NOMATCH_ONGOING : + nsIAutoCompleteResult::RESULT_NOMATCH); + mCurrentResult->SetDefaultIndex(-1); + } + + rv = mCurrentResult->SetListener(this); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentListener->OnSearchResult(this, mCurrentResult); + + // if we're not done searching, adjust our end time and + // search the next earlier chunk of time + if (moreChunksToSearch) { + mCurrentChunkEndTime -= AUTOCOMPLETE_SEARCH_CHUNK; + rv = StartAutoCompleteTimer(AUTOCOMPLETE_SEARCH_TIMEOUT); + NS_ENSURE_SUCCESS(rv, rv); + } + return rv; +} // nsNavHistory::StartSearch // @@ -309,56 +215,100 @@ nsNavHistory::StartSearch(const nsAString & aSearchString, nsIAutoCompleteResult *aPreviousResult, nsIAutoCompleteObserver *aListener) { + NS_ENSURE_ARG_POINTER(aListener); + mCurrentSearchString = aSearchString; + // remove whitespace, see bug #392141 for details + mCurrentSearchString.Trim(" \r\n\t\b"); + mCurrentListener = aListener; nsresult rv; - NS_ENSURE_ARG_POINTER(aListener); + // determine if we can start by searching through the previous search results. + // if we can't, we need to reset mCurrentChunkEndTime and mCurrentOldestVisit. + // if we can, we will search through our previous search results and then resume + // searching using the previous mCurrentChunkEndTime and mCurrentOldestVisit values. + PRBool searchPrevious = PR_FALSE; + if (aPreviousResult) { + PRUint32 matchCount = 0; + aPreviousResult->GetMatchCount(&matchCount); + nsAutoString prevSearchString; + aPreviousResult->GetSearchString(prevSearchString); - nsCOMPtr result = - do_CreateInstance(NS_AUTOCOMPLETESIMPLERESULT_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - result->SetSearchString(aSearchString); - - // Performance: We can improve performance for refinements of a previous - // result by filtering the old result with the new string. However, since - // our results are not a full match of history, we'll need to requery if - // any of the subresults returned the maximum number of elements (i.e. we - // didn't load all of them). - // - // Timing measurements show that the performance of this is actually very - // good for specific queries. Thus, the times where we can do the - // optimization (when there are few results) are exactly the times when - // we don't have to. As a result, we keep it this much simpler way. - if (aSearchString.IsEmpty()) { - rv = AutoCompleteTypedSearch(result); - } else { - rv = AutoCompleteFullHistorySearch(aSearchString, result); + // if search string begins with the previous search string, it's a go + searchPrevious = Substring(mCurrentSearchString, 0, + prevSearchString.Length()).Equals(prevSearchString); } - NS_ENSURE_SUCCESS(rv, rv); + else { + // reset to mCurrentChunkEndTime + mCurrentChunkEndTime = PR_Now(); - // Determine the result of the search - PRUint32 count; - result->GetMatchCount(&count); - if (count > 0) { - result->SetSearchResult(nsIAutoCompleteResult::RESULT_SUCCESS); - result->SetDefaultIndex(0); - } else { - result->SetSearchResult(nsIAutoCompleteResult::RESULT_NOMATCH); - result->SetDefaultIndex(-1); + // determine our earliest visit + nsCOMPtr dbSelectStatement; + rv = mDBConn->CreateStatement( + NS_LITERAL_CSTRING("SELECT MIN(visit_date) id FROM moz_historyvisits WHERE visit_type <> 4 AND visit_type <> 0"), + getter_AddRefs(dbSelectStatement)); + NS_ENSURE_SUCCESS(rv, rv); + PRBool hasMinVisit; + rv = dbSelectStatement->ExecuteStep(&hasMinVisit); + NS_ENSURE_SUCCESS(rv, rv); + + if (hasMinVisit) { + rv = dbSelectStatement->GetInt64(0, &mCurrentOldestVisit); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + // if we have no visits, use a reasonable value + mCurrentOldestVisit = PR_Now() - USECS_PER_DAY; + } } - rv = result->SetListener(this); + mCurrentResult = do_CreateInstance(NS_AUTOCOMPLETESIMPLERESULT_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); - aListener->OnSearchResult(this, result); + mCurrentResultURLs.Clear(); + + // Search through the previous result + if (searchPrevious) { + PRUint32 matchCount; + aPreviousResult->GetMatchCount(&matchCount); + for (PRInt32 i = 0; i < matchCount; i++) { + nsAutoString url, title; + aPreviousResult->GetValueAt(i, url); + aPreviousResult->GetCommentAt(i, title); + + PRBool isMatch = CaseInsensitiveFindInReadable(mCurrentSearchString, url); + if (!isMatch) + isMatch = CaseInsensitiveFindInReadable(mCurrentSearchString, title); + + if (isMatch) { + nsAutoString image, style; + aPreviousResult->GetImageAt(i, image); + aPreviousResult->GetStyleAt(i, style); + + mCurrentResultURLs.Put(url, PR_TRUE); + + rv = mCurrentResult->AppendMatch(url, title, image, style); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + // fire right away, we already waited to start searching + rv = StartAutoCompleteTimer(0); + NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } - // nsNavHistory::StopSearch NS_IMETHODIMP nsNavHistory::StopSearch() { + if (mAutoCompleteTimer) + mAutoCompleteTimer->Cancel(); + + mCurrentSearchString.Truncate(); + mCurrentListener = nsnull; + return NS_OK; } @@ -371,169 +321,104 @@ nsNavHistory::StopSearch() // is no URL information to use. The ordering just comes out of the DB by // visit count (primary) and time since last visited (secondary). -nsresult nsNavHistory::AutoCompleteTypedSearch( - nsIAutoCompleteSimpleResult* result) +nsresult nsNavHistory::AutoCompleteTypedSearch() { - // need to get more than the required minimum number since some will be dupes nsCOMPtr dbSelectStatement; + nsCString sql = NS_LITERAL_CSTRING( - "SELECT h.url, title, f.url " - "FROM moz_historyvisits v JOIN moz_places h ON v.place_id = h.id " - "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id " - "WHERE h.typed = 1 ORDER BY visit_date DESC LIMIT "); - sql.AppendInt(AUTOCOMPLETE_MAX_PER_TYPED * 3); - nsresult rv = mDBConn->CreateStatement(sql, getter_AddRefs(dbSelectStatement)); - NS_ENSURE_SUCCESS(rv, rv); - - // prevent duplicates - nsDataHashtable urls; - if (! urls.Init(500)) - return NS_ERROR_OUT_OF_MEMORY; - + "SELECT h.url, h.title, f.url, b.id " + "FROM moz_places h " + "LEFT OUTER JOIN moz_bookmarks b ON b.fk = h.id " + "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id " + "JOIN moz_historyvisits v ON h.id = v.place_id WHERE (h.id IN " + "(SELECT DISTINCT h.id from moz_historyvisits v, moz_places h WHERE " + "v.place_id = h.id AND h.typed = 1 AND v.visit_type <> 0 AND v.visit_type <> 4 " + "ORDER BY v.visit_date DESC LIMIT "); + sql.AppendInt(AUTOCOMPLETE_MAX_PER_TYPED); + sql += NS_LITERAL_CSTRING(")) GROUP BY h.id ORDER BY MAX(v.visit_date) DESC"); + nsFaviconService* faviconService = nsFaviconService::GetFaviconService(); NS_ENSURE_TRUE(faviconService, NS_ERROR_OUT_OF_MEMORY); - PRInt32 dummy; - PRInt32 count = 0; + nsresult rv = mDBConn->CreateStatement(sql, getter_AddRefs(dbSelectStatement)); + NS_ENSURE_SUCCESS(rv, rv); + PRBool hasMore = PR_FALSE; - while (count < AUTOCOMPLETE_MAX_PER_TYPED && - NS_SUCCEEDED(dbSelectStatement->ExecuteStep(&hasMore)) && hasMore) { - nsAutoString entryURL, entryTitle, entryImage; - dbSelectStatement->GetString(0, entryURL); - dbSelectStatement->GetString(1, entryTitle); - dbSelectStatement->GetString(2, entryImage); - - if (! urls.Get(entryURL, &dummy)) { - // new item - nsCAutoString faviconSpec; - faviconService->GetFaviconSpecForIconString( - NS_ConvertUTF16toUTF8(entryImage), faviconSpec); - rv = result->AppendMatch(entryURL, entryTitle, - NS_ConvertUTF8toUTF16(faviconSpec), NS_LITERAL_STRING("favicon")); - NS_ENSURE_SUCCESS(rv, rv); - - urls.Put(entryURL, 1); - count ++; - } - } + while (NS_SUCCEEDED(dbSelectStatement->ExecuteStep(&hasMore)) && hasMore) { + nsAutoString entryURL, entryTitle, entryFavicon; + dbSelectStatement->GetString(kAutoCompleteIndex_URL, entryURL); + dbSelectStatement->GetString(kAutoCompleteIndex_Title, entryTitle); + dbSelectStatement->GetString(kAutoCompleteIndex_FaviconURL, entryFavicon); + PRInt64 itemId = 0; + dbSelectStatement->GetInt64(kAutoCompleteIndex_ItemId, &itemId); + nsCAutoString imageSpec; + faviconService->GetFaviconSpecForIconString( + NS_ConvertUTF16toUTF8(entryFavicon), imageSpec); + rv = mCurrentResult->AppendMatch(entryURL, entryTitle, + NS_ConvertUTF8toUTF16(imageSpec), itemId ? NS_LITERAL_STRING("bookmark") : NS_LITERAL_STRING("favicon")); + NS_ENSURE_SUCCESS(rv, rv); + } return NS_OK; } // nsNavHistory::AutoCompleteFullHistorySearch // -// A brute-force search of the entire history. This matches the given input -// with every possible history entry, and sorts them by likelihood. +// Search history for visits that have a url or title that contains mCurrentSearchString +// and are within our current chunk of time: +// between (mCurrentChunkEndTime - AUTOCOMPLETE_SEARCH_CHUNK) and (mCurrentChunkEndTime) // -// This may be slow for people on slow computers with large histories. nsresult -nsNavHistory::AutoCompleteFullHistorySearch(const nsAString& aSearchString, - nsIAutoCompleteSimpleResult* aResult) +nsNavHistory::AutoCompleteFullHistorySearch() { - nsString searchString; - nsresult rv = NormalizeAutocompleteInput(aSearchString, searchString); - if (NS_FAILED(rv)) - return NS_OK; // no matches for invalid input + mozStorageStatementScoper scope(mDBAutoCompleteQuery); - nsTArray matches; + nsresult rv = mDBAutoCompleteQuery->BindInt64Parameter(0, mCurrentChunkEndTime - AUTOCOMPLETE_SEARCH_CHUNK); + NS_ENSURE_SUCCESS(rv, rv); - // Try a query using this search string and every prefix. Keep track of - // known prefixes that the input matches for exclusion later - PRUint32 i; - const nsTArray emptyArray; - nsTArray firstLevelMatches; - nsTArray secondLevelMatches; - for (i = 0; i < mAutoCompletePrefixes.Length(); i ++) { - if (StringBeginsWith(mAutoCompletePrefixes[i].prefix, searchString)) { - if (mAutoCompletePrefixes[i].secondLevel) - secondLevelMatches.AppendElement(i); - else - firstLevelMatches.AppendElement(i); - } + rv = mDBAutoCompleteQuery->BindInt64Parameter(1, mCurrentChunkEndTime); + NS_ENSURE_SUCCESS(rv, rv); - // current string to search for - nsString cur = mAutoCompletePrefixes[i].prefix + searchString; + nsString escapedSearchString; + rv = mDBAutoCompleteQuery->EscapeStringForLIKE(mCurrentSearchString, PRUnichar('/'), escapedSearchString); + NS_ENSURE_SUCCESS(rv, rv); - // see if the concatenated string itself matches any prefixes - nsTArray curPrefixMatches; - for (PRUint32 prefix = 0; prefix < mAutoCompletePrefixes.Length(); prefix ++) { - if (StringBeginsWith(mAutoCompletePrefixes[prefix].prefix, cur)) - curPrefixMatches.AppendElement(prefix); - } - - // search for the current string, excluding those matching prefixes - AutoCompleteQueryOnePrefix(cur, curPrefixMatches, 0, &matches); - - // search for each of those matching prefixes, applying the prefix penalty - for (PRUint32 match = 0; match < curPrefixMatches.Length(); match ++) { - AutoCompleteQueryOnePrefix(mAutoCompletePrefixes[curPrefixMatches[match]].prefix, - emptyArray, AUTOCOMPLETE_MATCHES_PREFIX_PENALTY, - &matches); - } - } - - // Now try searching with no prefix - if (firstLevelMatches.Length() > 0) { - // This will get all matches that DON'T match any prefix. For example, if - // the user types "http://w" we will match "http://westinghouse.com" but - // not "http://www.something". - AutoCompleteQueryOnePrefix(searchString, - firstLevelMatches, 0, &matches); - } else if (secondLevelMatches.Length() > 0) { - // if there are no first level matches (i.e. "http://") then we fall back on - // second level matches. Here, we assume that a first level match implies - // a second level match as well, so we only have to check when there are no - // first level matches. - AutoCompleteQueryOnePrefix(searchString, - secondLevelMatches, 0, &matches); - - // now we try to fill in matches of the prefix. For example, if you type - // "http://w" we will still match against "http://www." but with a penalty. - // We only do this for second level prefixes. - for (PRUint32 match = 0; match < secondLevelMatches.Length(); match ++) { - AutoCompleteQueryOnePrefix(mAutoCompletePrefixes[secondLevelMatches[match]].prefix, - emptyArray, AUTOCOMPLETE_MATCHES_SCHEME_PENALTY, - &matches); - } - } else { - // Input matched no prefix, try to query for all URLs beinning with this - // exact input. - AutoCompleteQueryOnePrefix(searchString, emptyArray, - AUTOCOMPLETE_MATCHES_SCHEME_PENALTY, &matches); - } + // prepend and append with % for "contains" + rv = mDBAutoCompleteQuery->BindStringParameter(2, NS_LITERAL_STRING("%") + escapedSearchString + NS_LITERAL_STRING("%")); + NS_ENSURE_SUCCESS(rv, rv); nsFaviconService* faviconService = nsFaviconService::GetFaviconService(); NS_ENSURE_TRUE(faviconService, NS_ERROR_OUT_OF_MEMORY); - // fill into result - if (matches.Length() > 0) { - // sort according to priorities - AutoCompleteResultComparator comparator(this); - matches.Sort(comparator); + PRBool hasMore = PR_FALSE; - nsCAutoString faviconSpec; - faviconService->GetFaviconSpecForIconString( - NS_ConvertUTF16toUTF8(matches[0].image), faviconSpec); - rv = aResult->AppendMatch(matches[0].url, matches[0].title, - NS_ConvertUTF8toUTF16(faviconSpec), - NS_LITERAL_STRING("favicon")); - NS_ENSURE_SUCCESS(rv, rv); + // Determine the result of the search + while (NS_SUCCEEDED(mDBAutoCompleteQuery->ExecuteStep(&hasMore)) && hasMore) { + nsAutoString entryURL, entryTitle, entryFavicon; + mDBAutoCompleteQuery->GetString(kAutoCompleteIndex_URL, entryURL); + mDBAutoCompleteQuery->GetString(kAutoCompleteIndex_Title, entryTitle); + mDBAutoCompleteQuery->GetString(kAutoCompleteIndex_FaviconURL, entryFavicon); + PRInt64 itemId = 0; + mDBAutoCompleteQuery->GetInt64(kAutoCompleteIndex_ItemId, &itemId); - for (i = 1; i < matches.Length(); i ++) { - // only add ones that are NOT the same as the previous one. It's possible - // to get duplicates from the queries. - if (!matches[i].url.Equals(matches[i-1].url)) { - faviconService->GetFaviconSpecForIconString( - NS_ConvertUTF16toUTF8(matches[i].image), faviconSpec); - rv = aResult->AppendMatch(matches[i].url, matches[i].title, - NS_ConvertUTF8toUTF16(faviconSpec), - NS_LITERAL_STRING("favicon")); - NS_ENSURE_SUCCESS(rv, rv); - } + PRBool dummy; + // prevent duplicates. this can happen when chunking as we + // may have already seen this URL from an earlier chunk of time + if (!mCurrentResultURLs.Get(entryURL, &dummy)) { + // new item, append to our results and put it in our hash table. + nsCAutoString faviconSpec; + faviconService->GetFaviconSpecForIconString( + NS_ConvertUTF16toUTF8(entryFavicon), faviconSpec); + rv = mCurrentResult->AppendMatch(entryURL, entryTitle, + NS_ConvertUTF8toUTF16(faviconSpec), itemId ? NS_LITERAL_STRING("bookmark") : NS_LITERAL_STRING("favicon")); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentResultURLs.Put(entryURL, PR_TRUE); } } + return NS_OK; } @@ -556,177 +441,3 @@ nsNavHistory::OnValueRemoved(nsIAutoCompleteSimpleResult* aResult, return NS_OK; } - -// nsNavHistory::AutoCompleteQueryOnePrefix -// -// The values in aExcludePrefixes are indices into mAutoCompletePrefixes -// of prefixes to exclude during this query. For example, if I type -// "ht" this function will be called to match everything starting with -// "ht" EXCEPT "http://" and "https://". - -nsresult -nsNavHistory::AutoCompleteQueryOnePrefix(const nsString& aSearchString, - const nsTArray& aExcludePrefixes, - PRInt32 aPriorityDelta, - nsTArray* aResult) -{ - // All URL queries are in UTF-8. Compute the beginning (inclusive) and - // ending (exclusive) of the range of URLs to include when compared - // using strcmp (which is what sqlite does). - nsCAutoString beginQuery = NS_ConvertUTF16toUTF8(aSearchString); - if (beginQuery.IsEmpty()) - return NS_OK; - nsCAutoString endQuery = beginQuery; - unsigned char maxChar[6] = { 0xfd, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf }; - endQuery.Append(reinterpret_cast(maxChar), 6); - - nsTArray ranges; - if (aExcludePrefixes.Length() > 0) { - // we've been requested to include holes in our range, sort these ranges - ranges.AppendElement(beginQuery); - for (PRUint32 i = 0; i < aExcludePrefixes.Length(); i ++) { - nsCAutoString thisPrefix = NS_ConvertUTF16toUTF8( - mAutoCompletePrefixes[aExcludePrefixes[i]].prefix); - ranges.AppendElement(thisPrefix); - thisPrefix.Append(reinterpret_cast(maxChar), 6); - ranges.AppendElement(thisPrefix); - } - ranges.AppendElement(endQuery); - ranges.Sort(); - } else { - // simple range with no holes - ranges.AppendElement(beginQuery); - ranges.AppendElement(endQuery); - } - - NS_ASSERTION(ranges.Length() % 2 == 0, "Ranges should be pairs!"); - - // The nested select expands to nonzero when the item is bookmarked. - // It might be nice to also select hidden bookmarks (unvisited) but that - // made this statement more complicated and should be an unusual case. - nsresult rv; - for (PRUint32 range = 0; range < ranges.Length() - 1; range += 2) { - mozStorageStatementScoper scoper(mDBAutoCompleteQuery); - - rv = mDBAutoCompleteQuery->BindUTF8StringParameter(0, ranges[range]); - NS_ENSURE_SUCCESS(rv, rv); - rv = mDBAutoCompleteQuery->BindUTF8StringParameter(1, ranges[range + 1]); - NS_ENSURE_SUCCESS(rv, rv); - - PRBool hasMore; - nsAutoString url, title, image; - while (NS_SUCCEEDED(mDBAutoCompleteQuery->ExecuteStep(&hasMore)) && hasMore) { - mDBAutoCompleteQuery->GetString(0, url); - mDBAutoCompleteQuery->GetString(1, title); - PRInt32 visitCount = mDBAutoCompleteQuery->AsInt32(2); - PRInt32 priority = ComputeAutoCompletePriority(url, visitCount, - mDBAutoCompleteQuery->AsInt32(3) > 0, - mDBAutoCompleteQuery->AsInt32(4) > 0) + aPriorityDelta; - mDBAutoCompleteQuery->GetString(5, image); - aResult->AppendElement(AutoCompleteIntermediateResult( - url, title, image, visitCount, priority)); - } - } - return NS_OK; -} - - -// nsNavHistory::AutoCompleteGetPrefixLength - -PRInt32 -nsNavHistory::AutoCompleteGetPrefixLength(const nsString& aSpec) -{ - for (PRUint32 i = 0; i < mAutoCompletePrefixes.Length(); ++i) { - if (StringBeginsWith(aSpec, mAutoCompletePrefixes[i].prefix)) - return mAutoCompletePrefixes[i].prefix.Length(); - } - return 0; // no prefix -} - - -// ComputeAutoCompletePriority -// -// Favor websites and webpaths more than webpages by boosting -// their visit counts. This assumes that URLs have been normalized, -// appending a trailing '/'. -// -// We use addition to boost the visit count rather than multiplication -// since we want URLs with large visit counts to remain pretty much -// in raw visit count order - we assume the user has visited these urls -// often for a reason and there shouldn't be a problem with putting them -// high in the autocomplete list regardless of whether they are sites/ -// paths or pages. However for URLs visited only a few times, sites -// & paths should be presented before pages since they are generally -// more likely to be visited again. - -PRInt32 -ComputeAutoCompletePriority(const nsAString& aUrl, PRInt32 aVisitCount, - PRBool aWasTyped, PRBool aIsBookmarked) -{ - PRInt32 aPriority = aVisitCount; - - if (!aUrl.IsEmpty()) { - // url is a site/path if it has a trailing slash - if (aUrl.Last() == PRUnichar('/')) - aPriority += AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST; - } - - if (aWasTyped) - aPriority += AUTOCOMPLETE_TYPED_BOOST; - if (aIsBookmarked) - aPriority += AUTOCOMPLETE_BOOKMARKED_BOOST; - - return aPriority; -} - - -// NormalizeAutocompleteInput - -nsresult NormalizeAutocompleteInput(const nsAString& aInput, - nsString& aOutput) -{ - nsresult rv; - - if (aInput.IsEmpty()) { - aOutput.Truncate(); - return NS_OK; - } - nsCAutoString input = NS_ConvertUTF16toUTF8(aInput); - - nsCOMPtr uri; - rv = NS_NewURI(getter_AddRefs(uri), input); - PRBool isSchemeAdded = PR_FALSE; - if (NS_FAILED(rv)) { - // it may have failed because there is no scheme, prepend one - isSchemeAdded = PR_TRUE; - input = NS_LITERAL_CSTRING("http://") + input; - - rv = NS_NewURI(getter_AddRefs(uri), input); - if (NS_FAILED(rv)) - return rv; // still not valid, can't autocomplete this URL - } - - nsCAutoString spec; - rv = uri->GetSpec(spec); - NS_ENSURE_SUCCESS(rv, rv); - if (spec.IsEmpty()) - return NS_OK; // should never happen but we assume it's not empty below, so check - - aOutput = NS_ConvertUTF8toUTF16(spec); - - // trim the "http://" scheme if we added it - if (isSchemeAdded) { - NS_ASSERTION(aOutput.Length() > 7, "String impossibly short"); - aOutput = Substring(aOutput, 7); - } - - // it may have appended a slash, get rid of it - // example: input was "http://www.mozil" the URI spec will be - // "http://www.mozil/" which is obviously wrong to complete against. - // However, it won't always append a slash, for example, for the input - // "http://www.mozilla.org/supp" - if (input[input.Length() - 1] != '/' && aOutput[aOutput.Length() - 1] == '/') - aOutput.Truncate(aOutput.Length() - 1); - - return NS_OK; -} diff --git a/toolkit/components/places/src/nsNavHistoryExpire.cpp b/toolkit/components/places/src/nsNavHistoryExpire.cpp index 7a154f93bf2d..594d4d398bba 100644 --- a/toolkit/components/places/src/nsNavHistoryExpire.cpp +++ b/toolkit/components/places/src/nsNavHistoryExpire.cpp @@ -86,9 +86,9 @@ struct nsNavHistoryExpireRecord { #define MAX_SEQUENTIAL_RUNS 1 // Expiration policy amounts (in microseconds) -const PRTime EXPIRATION_POLICY_DAYS = (7 * 86400 * PR_MSEC_PER_SEC); -const PRTime EXPIRATION_POLICY_WEEKS = (30 * 86400 * PR_MSEC_PER_SEC); -const PRTime EXPIRATION_POLICY_MONTHS = ((PRTime)180 * 86400 * PR_MSEC_PER_SEC); +const PRTime EXPIRATION_POLICY_DAYS = ((PRTime)7 * 86400 * PR_USEC_PER_SEC); +const PRTime EXPIRATION_POLICY_WEEKS = ((PRTime)30 * 86400 * PR_USEC_PER_SEC); +const PRTime EXPIRATION_POLICY_MONTHS = ((PRTime)180 * 86400 * PR_USEC_PER_SEC); // nsNavHistoryExpire::nsNavHistoryExpire // @@ -402,18 +402,22 @@ nsresult nsNavHistoryExpire::EraseVisits(mozIStorageConnection* aConnection, const nsTArray& aRecords) { - nsCOMPtr deleteStatement; - nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( - "DELETE FROM moz_historyvisits WHERE id = ?1"), - getter_AddRefs(deleteStatement)); - NS_ENSURE_SUCCESS(rv, rv); - PRUint32 i; - for (i = 0; i < aRecords.Length(); i ++) { - deleteStatement->BindInt64Parameter(0, aRecords[i].visitID); - rv = deleteStatement->Execute(); - NS_ENSURE_SUCCESS(rv, rv); + // build a comma separated string of visit ids to delete + nsCAutoString deletedVisitIds; + for (PRUint32 i = 0; i < aRecords.Length(); i ++) { + // Do not add comma separator for the first entry + if (! deletedVisitIds.IsEmpty()) + deletedVisitIds.AppendLiteral(", "); + deletedVisitIds.AppendInt(aRecords[i].visitID); } - return NS_OK; + + if (deletedVisitIds.IsEmpty()) + return NS_OK; + + return aConnection->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("DELETE FROM moz_historyvisits WHERE id IN (") + + deletedVisitIds + + NS_LITERAL_CSTRING(")")); } @@ -429,37 +433,29 @@ nsresult nsNavHistoryExpire::EraseHistory(mozIStorageConnection* aConnection, nsTArray& aRecords) { - nsCOMPtr deleteStatement; - nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( - "DELETE FROM moz_places WHERE id = ?1"), - getter_AddRefs(deleteStatement)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr selectStatement; - rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( - "SELECT place_id FROM moz_historyvisits WHERE place_id = ?1"), - getter_AddRefs(selectStatement)); - NS_ENSURE_SUCCESS(rv, rv); - + // build a comma separated string of place ids to delete + nsCAutoString deletedPlaceIds; for (PRUint32 i = 0; i < aRecords.Length(); i ++) { - if (aRecords[i].bookmarked) - continue; // don't delete bookmarked entries - if (StringBeginsWith(aRecords[i].uri, NS_LITERAL_CSTRING("place:"))) - continue; // don't delete "place" URIs - - // check that there are no visits - rv = selectStatement->BindInt64Parameter(0, aRecords[i].placeID); - NS_ENSURE_SUCCESS(rv, rv); - PRBool hasVisit = PR_FALSE; - rv = selectStatement->ExecuteStep(&hasVisit); - selectStatement->Reset(); - if (hasVisit) continue; - + // IF bookmarked entries OR "place" URIs do not delete + if (aRecords[i].bookmarked || + StringBeginsWith(aRecords[i].uri, NS_LITERAL_CSTRING("place:"))) + continue; + // Do not add comma separator for the first entry + if (! deletedPlaceIds.IsEmpty()) + deletedPlaceIds.AppendLiteral(", "); + deletedPlaceIds.AppendInt(aRecords[i].placeID); aRecords[i].erased = PR_TRUE; - rv = deleteStatement->BindInt64Parameter(0, aRecords[i].placeID); - rv = deleteStatement->Execute(); } - return NS_OK; + + if (deletedPlaceIds.IsEmpty()) + return NS_OK; + + return aConnection->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("DELETE FROM moz_places WHERE id IN (") + + deletedPlaceIds + + NS_LITERAL_CSTRING(") AND id IN (SELECT h.id FROM moz_places h " + "LEFT OUTER JOIN moz_historyvisits v ON h.id = v.place_id " + "WHERE v.id IS NULL)")); } @@ -469,41 +465,29 @@ nsresult nsNavHistoryExpire::EraseFavicons(mozIStorageConnection* aConnection, const nsTArray& aRecords) { - // see if this favicon still has an entry - nsCOMPtr selectStatement; - nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( - "SELECT id FROM moz_places where favicon_id = ?1"), - getter_AddRefs(selectStatement)); - NS_ENSURE_SUCCESS(rv, rv); - - // delete a favicon - nsCOMPtr deleteStatement; - rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( - "DELETE FROM moz_favicons WHERE id = ?1"), - getter_AddRefs(deleteStatement)); - NS_ENSURE_SUCCESS(rv, rv); - + // build a comma separated string of favicon ids to delete + nsCAutoString deletedFaviconIds; for (PRUint32 i = 0; i < aRecords.Length(); i ++) { - if (! aRecords[i].erased) - continue; // main entry not expired - if (aRecords[i].faviconID == 0) - continue; // no favicon - selectStatement->BindInt64Parameter(0, aRecords[i].faviconID); - - // see if there are any history entries and skip if so - PRBool hasEntry; - if (NS_SUCCEEDED(selectStatement->ExecuteStep(&hasEntry)) && hasEntry) { - selectStatement->Reset(); - continue; // favicon still referenced - } - selectStatement->Reset(); - - // delete the favicon, ignoring errors. We could have the same favicon - // referenced twice in our list, and we'd try to delete it twice. - deleteStatement->BindInt64Parameter(0, aRecords[i].faviconID); - deleteStatement->Execute(); + // IF main entry not expired OR no favicon DO NOT DELETE + if (! aRecords[i].erased || aRecords[i].faviconID == 0) + continue; + // Do not add comma separator for the first entry + if (! deletedFaviconIds.IsEmpty()) + deletedFaviconIds.AppendLiteral(", "); + deletedFaviconIds.AppendInt(aRecords[i].faviconID); } - return NS_OK; + + if (deletedFaviconIds.IsEmpty()) + return NS_OK; + + // delete only if id is not referenced in moz_places + return aConnection->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("DELETE FROM moz_favicons WHERE id IN (") + + deletedFaviconIds + + NS_LITERAL_CSTRING(") AND id IN " + "(SELECT f.id FROM moz_favicons f " + "LEFT OUTER JOIN moz_places h ON f.id = h.favicon_id " + "WHERE h.favicon_id IS NULL)")); } diff --git a/toolkit/components/places/tests/unit/test_expiration.js b/toolkit/components/places/tests/unit/test_expiration.js index 6416285bf665..5f3523c3c687 100644 --- a/toolkit/components/places/tests/unit/test_expiration.js +++ b/toolkit/components/places/tests/unit/test_expiration.js @@ -199,8 +199,8 @@ function run_test() { annosvc.setPageAnnotation(testURI, testAnnoName, testAnnoVal, 0, annosvc.EXPIRE_DAYS); annosvc.setItemAnnotation(bookmark, testAnnoName, testAnnoVal, 0, annosvc.EXPIRE_DAYS); - // set dateAdded to 7 days ago - var expirationDate = (Date.now() - (7 * 86400 * 1000)) * 1000; + // set dateAdded to 8 days ago + var expirationDate = (Date.now() - (8 * 86400 * 1000)) * 1000; dbConnection.executeSimpleSQL("UPDATE moz_annos SET dateAdded = " + expirationDate); dbConnection.executeSimpleSQL("UPDATE moz_items_annos SET dateAdded = " + expirationDate); @@ -236,11 +236,40 @@ function run_test() { do_throw("bookmark still had days anno"); } catch(ex) {} - // test anno expiration (weeks) + // test anno expiration (days) removes annos annos 6 days old + histsvc.addVisit(testURI, Date.now(), 0, histsvc.TRANSITION_TYPED, false, 0); + annosvc.setPageAnnotation(testURI, testAnnoName, testAnnoVal, 0, annosvc.EXPIRE_DAYS); + annosvc.setItemAnnotation(bookmark, testAnnoName, testAnnoVal, 0, annosvc.EXPIRE_DAYS); + // these annotations should remain as they are only 6 days old + var expirationDate = (Date.now() - (6 * 86400 * 1000)) * 1000; + dbConnection.executeSimpleSQL("UPDATE moz_annos SET dateAdded = " + expirationDate); + dbConnection.executeSimpleSQL("UPDATE moz_items_annos SET dateAdded = " + expirationDate); + + // add a uri and then remove it, to trigger expiration + histsvc.addVisit(triggerURI, Date.now(), 0, histsvc.TRANSITION_TYPED, false, 0); + bhist.removePage(triggerURI); + + // test for unexpired annos + try { + do_check_eq(annosvc.getPageAnnotation(testURI, testAnnoName), testAnnoVal); + } catch(ex) { + do_throw("anno < 7 days old was expired!"); + } + annosvc.removePageAnnotation(testURI, testAnnoName); + try { + do_check_eq(annosvc.getItemAnnotation(bookmark, testAnnoName), testAnnoVal); + } catch(ex) { + do_throw("item anno < 7 days old was expired!"); + } + annosvc.removeItemAnnotation(bookmark, testAnnoName); + + + // test anno expiration (weeks) removes annos 31 days old histsvc.addVisit(testURI, Date.now(), 0, histsvc.TRANSITION_TYPED, false, 0); annosvc.setPageAnnotation(testURI, testAnnoName, testAnnoVal, 0, annosvc.EXPIRE_WEEKS); annosvc.setItemAnnotation(bookmark, testAnnoName, testAnnoVal, 0, annosvc.EXPIRE_WEEKS); - var expirationDate = (Date.now() - (30 * 86400 * 1000)) * 1000; + // these annotations should not remain as they are 31 days old + var expirationDate = (Date.now() - (31 * 86400 * 1000)) * 1000; dbConnection.executeSimpleSQL("UPDATE moz_annos SET dateAdded = " + expirationDate); dbConnection.executeSimpleSQL("UPDATE moz_items_annos SET dateAdded = " + expirationDate); // these annotations should remain @@ -274,10 +303,37 @@ function run_test() { do_throw("bookmark still had weeks anno"); } catch(ex) {} - // test anno expiration (months) + // test anno expiration (weeks) does not remove annos 29 days old + histsvc.addVisit(testURI, Date.now(), 0, histsvc.TRANSITION_TYPED, false, 0); + annosvc.setPageAnnotation(testURI, testAnnoName, testAnnoVal, 0, annosvc.EXPIRE_WEEKS); + annosvc.setItemAnnotation(bookmark, testAnnoName, testAnnoVal, 0, annosvc.EXPIRE_WEEKS); + // these annotations should remain as they are only 29 days old + var expirationDate = (Date.now() - (29 * 86400 * 1000)) * 1000; + dbConnection.executeSimpleSQL("UPDATE moz_annos SET dateAdded = " + expirationDate); + dbConnection.executeSimpleSQL("UPDATE moz_items_annos SET dateAdded = " + expirationDate); + + // add a uri and then remove it, to trigger expiration + histsvc.addVisit(triggerURI, Date.now(), 0, histsvc.TRANSITION_TYPED, false, 0); + bhist.removePage(triggerURI); + + // test for unexpired annos + try { + do_check_eq(annosvc.getPageAnnotation(testURI, testAnnoName), testAnnoVal); + } catch(ex) { + do_throw("anno < 30 days old was expired!"); + } + annosvc.removePageAnnotation(testURI, testAnnoName); + try { + do_check_eq(annosvc.getItemAnnotation(bookmark, testAnnoName), testAnnoVal); + } catch(ex) { + do_throw("item anno < 30 days old was expired!"); + } + annosvc.removeItemAnnotation(bookmark, testAnnoName); + + // test anno expiration (months) removes annos 181 days old annosvc.setPageAnnotation(testURI, testAnnoName, testAnnoVal, 0, annosvc.EXPIRE_MONTHS); annosvc.setItemAnnotation(bookmark, testAnnoName, testAnnoVal, 0, annosvc.EXPIRE_MONTHS); - var expirationDate = (Date.now() - (180 * 86400 * 1000)) * 1000; + var expirationDate = (Date.now() - (181 * 86400 * 1000)) * 1000; dbConnection.executeSimpleSQL("UPDATE moz_annos SET dateAdded = " + expirationDate); dbConnection.executeSimpleSQL("UPDATE moz_items_annos SET dateAdded = " + expirationDate); // these annotations should remain @@ -311,6 +367,33 @@ function run_test() { do_throw("bookmark still had months anno"); } catch(ex) {} + // test anno expiration (months) does not remove annos 179 days old + histsvc.addVisit(testURI, Date.now(), 0, histsvc.TRANSITION_TYPED, false, 0); + annosvc.setPageAnnotation(testURI, testAnnoName, testAnnoVal, 0, annosvc.EXPIRE_MONTHS); + annosvc.setItemAnnotation(bookmark, testAnnoName, testAnnoVal, 0, annosvc.EXPIRE_MONTHS); + // these annotations should remain as they are only 179 days old + var expirationDate = (Date.now() - (179 * 86400 * 1000)) * 1000; + dbConnection.executeSimpleSQL("UPDATE moz_annos SET dateAdded = " + expirationDate); + dbConnection.executeSimpleSQL("UPDATE moz_items_annos SET dateAdded = " + expirationDate); + + // add a uri and then remove it, to trigger expiration + histsvc.addVisit(triggerURI, Date.now(), 0, histsvc.TRANSITION_TYPED, false, 0); + bhist.removePage(triggerURI); + + // test for unexpired annos + try { + do_check_eq(annosvc.getPageAnnotation(testURI, testAnnoName), testAnnoVal); + } catch(ex) { + do_throw("anno < 180 days old was expired!"); + } + annosvc.removePageAnnotation(testURI, testAnnoName); + try { + do_check_eq(annosvc.getItemAnnotation(bookmark, testAnnoName), testAnnoVal); + } catch(ex) { + do_throw("item anno < 180 days old was expired!"); + } + annosvc.removeItemAnnotation(bookmark, testAnnoName); + // test anno expiration (session) // XXX requires app restart diff --git a/toolkit/components/printing/content/printUtils.js b/toolkit/components/printing/content/printUtils.js index 030b1f6e5518..bdd2823608ba 100644 --- a/toolkit/components/printing/content/printUtils.js +++ b/toolkit/components/printing/content/printUtils.js @@ -39,8 +39,6 @@ # # ***** END LICENSE BLOCK ***** -var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; - var gPrintSettingsAreGlobal = false; var gSavePrintSettings = false; @@ -88,6 +86,12 @@ var PrintUtils = { } }, + // calling PrintUtils.printPreview() requires that you have three functions + // in the global scope: getPPBrowser(), which returns the browser element in + // the window print preview uses, getNavToolbox(), which returns the element + // (usually the main toolbox element) before which the print preview toolbar + // should be inserted, and getWebNavigation(), which returns the document's + // nsIWebNavigation object printPreview: function (aEnterPPCallback, aExitPPCallback, aWindow) { // if we're already in PP mode, don't set the callbacks; chances @@ -98,10 +102,12 @@ var PrintUtils = { this._onEnterPP = aEnterPPCallback; this._onExitPP = aExitPPCallback; } else { - // hide the toolbar here -- it will be shown in + // collapse the browser here -- it will be shown in // onEnterPrintPreview; this forces a reflow which fixes display // issues in bug 267422. - pptoolbar.hidden = true; + var browser = getPPBrowser(); + if (browser) + browser.collapsed = true; } this._webProgressPP = {}; @@ -122,7 +128,7 @@ var PrintUtils = { PPROMPTSVC.showProgress(this, webBrowserPrint, printSettings, this._obsPP, false, this._webProgressPP, ppParams, notifyOnOpen); if (ppParams.value) { - var webNav = getBrowser().webNavigation; + var webNav = getWebNavigation(); ppParams.value.docTitle = webNav.document.title; ppParams.value.docURL = webNav.currentURI.spec; } @@ -183,7 +189,6 @@ var PrintUtils = { return printSettings; }, - _chromeState: {}, _closeHandlerPP: null, _webProgressPP: null, _onEnterPP: null, @@ -223,25 +228,22 @@ var PrintUtils = { var printPreviewTB = document.getElementById("print-preview-toolbar"); if (printPreviewTB) { printPreviewTB.updateToolbar(); - printPreviewTB.hidden = false; + var browser = getPPBrowser(); + if (browser) + browser.collapsed = false; return; } // show the toolbar after we go into print preview mode so // that we can initialize the toolbar with total num pages + var XUL_NS = + "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; printPreviewTB = document.createElementNS(XUL_NS, "toolbar"); printPreviewTB.setAttribute("printpreview", true); printPreviewTB.setAttribute("id", "print-preview-toolbar"); -#ifdef MOZ_PHOENIX - getBrowser().parentNode.insertBefore(printPreviewTB, getBrowser()); - - // Tab browser... - if ("getStripVisibility" in getBrowser()) { - this._chromeState.hadTabStrip = getBrowser().getStripVisibility(); - getBrowser().setStripVisibilityTo(false); - } -#endif + var navToolbox = getNavToolbox(); + navToolbox.parentNode.insertBefore(printPreviewTB, navToolbox); // copy the window close handler if (document.documentElement.hasAttribute("onclose")) @@ -267,24 +269,17 @@ var PrintUtils = { { window.removeEventListener("keypress", this.onKeyPressPP, true); -#ifdef MOZ_THUNDERBIRD - BrowserExitPrintPreview(); // make the traditional call..don't do any of the inline toolbar browser stuff - return; -#endif - // restore the old close handler document.documentElement.setAttribute("onclose", this._closeHandlerPP); this._closeHandlerPP = null; - if ("getStripVisibility" in getBrowser()) - getBrowser().setStripVisibilityTo(this._chromeState.hadTabStrip); - var webBrowserPrint = this.getWebBrowserPrint(aWindow); webBrowserPrint.exitPrintPreview(); // remove the print preview toolbar + var navToolbox = getNavToolbox(); var printPreviewTB = document.getElementById("print-preview-toolbar"); - getBrowser().parentNode.removeChild(printPreviewTB); + navToolbox.parentNode.removeChild(printPreviewTB); var contentWindow = aWindow || window.content; contentWindow.focus(); diff --git a/toolkit/components/satchel/src/Makefile.in b/toolkit/components/satchel/src/Makefile.in index 061f53518957..e8126d24c77c 100644 --- a/toolkit/components/satchel/src/Makefile.in +++ b/toolkit/components/satchel/src/Makefile.in @@ -59,6 +59,7 @@ REQUIRES = \ layout \ docshell \ gfx \ + thebes \ necko \ widget \ content \ diff --git a/toolkit/components/startup/src/nsAppStartup.cpp b/toolkit/components/startup/src/nsAppStartup.cpp index 6ec6fa545ca3..b91b7a2e7a2e 100644 --- a/toolkit/components/startup/src/nsAppStartup.cpp +++ b/toolkit/components/startup/src/nsAppStartup.cpp @@ -207,9 +207,33 @@ nsAppStartup::Quit(PRUint32 aMode) if (!mRestart) mRestart = aMode & eRestart; - if (ferocity == eConsiderQuit && mConsiderQuitStopper == 0) { - // attempt quit if the last window has been unregistered/closed - ferocity = eAttemptQuit; + // If we're considering quitting, we will only do so if: + if (ferocity == eConsiderQuit) { + if (mConsiderQuitStopper == 0) { + // there are no windows... + ferocity = eAttemptQuit; + } +#ifdef XP_MACOSX + else if (mConsiderQuitStopper == 1) { + // ... or there is only a hiddenWindow left, and it's useless: + nsCOMPtr appShell + (do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); + + // Failure shouldn't be fatal, but will abort quit attempt: + if (!appShell) + return NS_OK; + + PRBool usefulHiddenWindow; + appShell->GetApplicationProvidedHiddenWindow(&usefulHiddenWindow); + nsCOMPtr hiddenWindow; + appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow)); + // If the one window is useful, we won't quit: + if (!hiddenWindow || usefulHiddenWindow) + return NS_OK; + + ferocity = eAttemptQuit; + } +#endif } /* Currently ferocity can never have the value of eForceQuit here. @@ -369,8 +393,13 @@ nsAppStartup::ExitLastWindowClosingSurvivalArea(void) NS_ASSERTION(mConsiderQuitStopper > 0, "consider quit stopper out of bounds"); --mConsiderQuitStopper; - if (!mShuttingDown && mRunning && mConsiderQuitStopper == 0) - Quit(eAttemptQuit); +#ifdef XP_MACOSX + if (!mShuttingDown && mRunning && (mConsiderQuitStopper <= 1)) + Quit(eConsiderQuit); +#else + if (!mShuttingDown && mRunning && (mConsiderQuitStopper == 0)) + Quit(eConsiderQuit); +#endif return NS_OK; } diff --git a/toolkit/components/typeaheadfind/src/Makefile.in b/toolkit/components/typeaheadfind/src/Makefile.in index 9bead7eb5c04..ab58e542d3a2 100755 --- a/toolkit/components/typeaheadfind/src/Makefile.in +++ b/toolkit/components/typeaheadfind/src/Makefile.in @@ -55,6 +55,7 @@ REQUIRES = content \ editor \ find \ gfx \ + thebes \ layout \ locale \ necko \ diff --git a/toolkit/components/viewsource/content/viewSource.js b/toolkit/components/viewsource/content/viewSource.js index 6243e8b71957..6277484f27b9 100644 --- a/toolkit/components/viewsource/content/viewSource.js +++ b/toolkit/components/viewsource/content/viewSource.js @@ -272,6 +272,25 @@ function onExitPP() toolbox.hidden = false; } +function getPPBrowser() +{ + return document.getElementById("content"); +} + +function getNavToolbox() +{ + return document.getElementById("appcontent"); +} + +function getWebNavigation() +{ + try { + return gBrowser.webNavigation; + } catch (e) { + return null; + } +} + function ViewSourceGoToLine() { var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] diff --git a/toolkit/content/contentAreaUtils.js b/toolkit/content/contentAreaUtils.js index 4acd1fc952f1..c3b5035d75c2 100644 --- a/toolkit/content/contentAreaUtils.js +++ b/toolkit/content/contentAreaUtils.js @@ -451,79 +451,35 @@ function getTargetFile(aFpP, aSkipPrompt) const nsILocalFile = Components.interfaces.nsILocalFile; - // ben 07/31/2003: - // |browser.download.defaultFolder| holds the default download folder for - // all files when the user has elected to have all files automatically - // download to a folder. The values of |defaultFolder| can be either their - // desktop, their downloads folder (My Documents\My Downloads) or some other - // location of their choosing (which is mapped to |browser.download.dir| - // This pref is _unset_ when the user has elected to be asked about where - // to place every download - this will force the prompt to ask the user - // where to put saved files. - var dir = null; + // For information on download folder preferences, see + // mozilla/browser/components/preferences/main.js + var useDownloadDir = prefs.getBoolPref("useDownloadDir"); - - function getSpecialFolderKey(aFolderType) - { - if (aFolderType == "Desktop") - return "Desk"; - - if (aFolderType != "Downloads") - throw "ASSERTION FAILED: folder type should be 'Desktop' or 'Downloads'"; - -#ifdef XP_WIN - return "Pers"; -#else -#ifdef XP_MACOSX - return "UsrDocs"; -#else - return "Home"; -#endif -#endif - } - - function getDownloadsFolder(aFolder) - { - var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"] - .getService(Components.interfaces.nsIProperties); - - var dir = fileLocator.get(getSpecialFolderKey(aFolder), Components.interfaces.nsILocalFile); - - var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"] - .getService(Components.interfaces.nsIStringBundleService); - bundle = bundle.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties"); - - var description = bundle.GetStringFromName("myDownloads"); - if (aFolder != "Desktop") - dir.append(description); - - return dir; - } - - switch (prefs.getIntPref("folderList")) { - case 0: - dir = getDownloadsFolder("Desktop") - break; - case 1: - dir = getDownloadsFolder("Downloads"); - break; - case 2: - dir = prefs.getComplexValue("dir", nsILocalFile); - break; - } - - if (!aSkipPrompt || !useDownloadDir || !dir) { - // If we're asking the user where to save the file, root the Save As... - // dialog on they place they last picked. - try { - dir = prefs.getComplexValue("lastDir", nsILocalFile); + var dir = null; + + try { + // On prompt operations, default to lastDir, on direct to folder + // downloads, default to the user's configured download folder. + // (right-click save image vs. drag-and-drop into download manager) + var lastDir = prefs.getComplexValue("lastDir", nsILocalFile); + var dnldMgr = Components.classes["@mozilla.org/download-manager;1"] + .getService(Components.interfaces.nsIDownloadManager); + if (!aSkipPrompt) { + dir = lastDir; + } else { + dir = dnldMgr.userDownloadsDirectory; } - catch (e) { - // No default download location. Default to desktop. + } catch (ex) { + } + + if (!aSkipPrompt || !useDownloadDir || !dir || (dir && !dir.exists())) { + // If we're asking the user where to save the file, root the Save As... + // dialog on the place they last picked. + if (!dir || (dir && !dir.exists())) { + // Default to desktop. var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"] .getService(Components.interfaces.nsIProperties); - - dir = fileLocator.get(getSpecialFolderKey("Desktop"), nsILocalFile); + dir = fileLocator.get("Desk", nsILocalFile); } var fp = makeFilePicker(); @@ -548,7 +504,7 @@ function getTargetFile(aFpP, aSkipPrompt) catch (e) { } } - + if (fp.show() == Components.interfaces.nsIFilePicker.returnCancel || !fp.file) return false; diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn index 1485620ba9ce..26d7e3f118b2 100644 --- a/toolkit/content/jar.mn +++ b/toolkit/content/jar.mn @@ -64,7 +64,6 @@ toolkit.jar: *+ content/global/bindings/spinbuttons.xml (widgets/spinbuttons.xml) *+ content/global/bindings/stringbundle.xml (widgets/stringbundle.xml) *+ content/global/bindings/tabbox.xml (widgets/tabbox.xml) -*+ content/global/bindings/tabbrowser.xml (widgets/tabbrowser.xml) *+ content/global/bindings/text.xml (widgets/text.xml) *+ content/global/bindings/textbox.xml (widgets/textbox.xml) *+ content/global/bindings/toolbar.xml (widgets/toolbar.xml) diff --git a/toolkit/content/tests/widgets/Makefile.in b/toolkit/content/tests/widgets/Makefile.in index c9ef667e2547..1fc9f02d61d7 100644 --- a/toolkit/content/tests/widgets/Makefile.in +++ b/toolkit/content/tests/widgets/Makefile.in @@ -68,6 +68,9 @@ _TEST_FILES = test_bug360220.xul \ test_datepicker.xul \ test_timepicker.xul \ xul_selectcontrol.js \ + test_panelfrommenu.xul \ + test_hiddenitems.xul \ + test_hiddenpaging.xul \ $(NULL) ifeq (,$(filter mac cocoa,$(MOZ_WIDGET_TOOLKIT))) diff --git a/toolkit/content/tests/widgets/test_hiddenitems.xul b/toolkit/content/tests/widgets/test_hiddenitems.xul new file mode 100644 index 000000000000..126b1b2ecb94 --- /dev/null +++ b/toolkit/content/tests/widgets/test_hiddenitems.xul @@ -0,0 +1,89 @@ + + + + + + + diff --git a/toolkit/content/tests/widgets/test_hiddenpaging.xul b/toolkit/content/tests/widgets/test_hiddenpaging.xul new file mode 100644 index 000000000000..3c106a8c6648 --- /dev/null +++ b/toolkit/content/tests/widgets/test_hiddenpaging.xul @@ -0,0 +1,128 @@ + + + + + + + diff --git a/toolkit/content/tests/widgets/test_panelfrommenu.xul b/toolkit/content/tests/widgets/test_panelfrommenu.xul new file mode 100644 index 000000000000..86063a671762 --- /dev/null +++ b/toolkit/content/tests/widgets/test_panelfrommenu.xul @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +

+

+ +
+
+ + +
diff --git a/toolkit/content/tests/widgets/window_menubar.xul b/toolkit/content/tests/widgets/window_menubar.xul index 6b163ebd0a93..1f884e0d1b3e 100644 --- a/toolkit/content/tests/widgets/window_menubar.xul +++ b/toolkit/content/tests/widgets/window_menubar.xul @@ -1,8 +1,11 @@ + + @@ -492,6 +495,49 @@ var popupTests = [ checkClosed("editmenu", testname); } }, +{ + testname: "F10 to activate menubar for tab deactivation", + events: [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ], + test: function() { synthesizeKey("VK_F10", { }); }, +}, +{ + testname: "Deactivate menubar with tab key", + events: [ "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu" ], + test: function() { synthesizeKey("VK_TAB", { }); }, +}, +{ + testname: "F10 to activate menubar for escape deactivation", + events: [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ], + test: function() { synthesizeKey("VK_F10", { }); }, +}, +{ + testname: "Deactivate menubar with escape key", + events: [ "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu" ], + test: function() { synthesizeKey("VK_ESCAPE", { }); }, +}, +{ + testname: "F10 to activate menubar for f10 deactivation", + events: [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ], + test: function() { synthesizeKey("VK_F10", { }); }, +}, +{ + testname: "Deactivate menubar with f10 key", + events: [ "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu" ], + test: function() { synthesizeKey("VK_F10", { }); }, +}, +{ + testname: "F10 to activate menubar for alt deactivation", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ], + test: function() { synthesizeKey("VK_F10", { }); }, +}, +{ + testname: "Deactivate menubar with alt key", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu" ], + test: function() { synthesizeKey("VK_ALT", { }); }, +} + ]; ]]> diff --git a/toolkit/content/widgets/autocomplete.xml b/toolkit/content/widgets/autocomplete.xml index 848a8c9eb084..aa808911ffe8 100644 --- a/toolkit/content/widgets/autocomplete.xml +++ b/toolkit/content/widgets/autocomplete.xml @@ -511,9 +511,8 @@ - - + @@ -776,7 +775,10 @@ this.mPopupOpen = true; ]]> - + + ]]> diff --git a/toolkit/content/widgets/browser.xml b/toolkit/content/widgets/browser.xml index 646e59fdc9c0..11b2f4d2e4da 100644 --- a/toolkit/content/widgets/browser.xml +++ b/toolkit/content/widgets/browser.xml @@ -343,7 +343,7 @@ .getService(Components.interfaces.nsILocaleService); var stringBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"] .getService(Components.interfaces.nsIStringBundleService); - var bundleURL = "chrome://global/locale/tabbrowser.properties"; + var bundleURL = "chrome://global/locale/browser.properties"; this._mStrBundle = stringBundleService.createBundle(bundleURL, localeService.getApplicationLocale()); } return this._mStrBundle; diff --git a/toolkit/content/widgets/listbox.xml b/toolkit/content/widgets/listbox.xml index 199252f2635c..36b26aaf4008 100644 --- a/toolkit/content/widgets/listbox.xml +++ b/toolkit/content/widgets/listbox.xml @@ -99,8 +99,9 @@ insertItemAt(aIndex, aLabel, aValue) /** Scroll up/down one page - * @param aDirection - specifies scrolling direction, should be either -1 - or 1 */ + * @param aDirection - specifies scrolling direction, should be either -1 or 1 + * @return the number of elements the selection scrolled + */ scrollOnePage(aDirection) /** Fire "select" event */ @@ -334,12 +335,15 @@ // Don't use clearSelection() because it causes a lot of noise // with respect to selection removed notifications used by the // accessibility API support. + var userSelecting = this._userSelecting; + this._userSelecting = false; // that's US automatically unselecting for (; currentItem; currentItem = this.getNextItem(currentItem, 1)) this.removeItemFromSelection(currentItem); for (currentItem = this.getItemAtIndex(0); currentItem != aStartItem; currentItem = this.getNextItem(currentItem, 1)) this.removeItemFromSelection(currentItem); + this._userSelecting = userSelecting; this._suppressOnSelect = suppressSelect; @@ -478,8 +482,13 @@ newIndex = numItems - 1; var newItem = this.getItemAtIndex(newIndex); + // make sure that the item is actually visible/selectable + if (this._userSelecting && newItem && !this._canUserSelect(newItem)) + newItem = + aOffset > 0 ? this.getNextItem(newItem, 1) || this.getPreviousItem(newItem, 1) : + this.getPreviousItem(newItem, 1) || this.getNextItem(newItem, 1); if (newItem) { - this.ensureIndexIsVisible(newIndex); + this.ensureIndexIsVisible(this.getIndexOfItem(newItem)); if (aIsSelectingRange) this.selectItemRange(null, newItem); else if (aIsSelecting) @@ -500,7 +509,8 @@ while (aStartItem) { aStartItem = aStartItem.nextSibling; if (aStartItem && aStartItem instanceof - Components.interfaces.nsIDOMXULSelectControlItemElement) { + Components.interfaces.nsIDOMXULSelectControlItemElement && + (!this._userSelecting || this._canUserSelect(aStartItem))) { --aDelta; if (aDelta == 0) return aStartItem; @@ -518,7 +528,8 @@ while (aStartItem) { aStartItem = aStartItem.previousSibling; if (aStartItem && aStartItem instanceof - Components.interfaces.nsIDOMXULSelectControlItemElement) { + Components.interfaces.nsIDOMXULSelectControlItemElement && + (!this._userSelecting || this._canUserSelect(aStartItem))) { --aDelta; if (aDelta == 0) return aStartItem; @@ -529,6 +540,28 @@ + + + + + + + + + + + + + + + @@ -538,6 +571,7 @@ false + false null null null @@ -545,22 +579,22 @@ = maxTop && maxTop > this.currentIndex) { - newTop = maxTop; + var maxTop = this.getRowCount() - this.getNumberOfVisibleRows(); + for (i = this.getRowCount(); i >= 0 && i > maxTop; i--) { + item = this.getItemAtIndex(i); + if (item && !this._canUserSelect(item)) + maxTop--; } + if (newTop >= maxTop) + newTop = maxTop; } - else if (newTop < 0) + if (newTop < 0) newTop = 0; this.scrollToIndex(newTop); return pageOffset; @@ -924,6 +970,7 @@ var control = this.control; if (!control || control.disabled) return; + control._userSelecting = true; if (control.selType != "multiple") { control.selectItem(this); } @@ -945,6 +992,7 @@ // doesn't de- and reselect this item if it is selected control.selectItemRange(this, this); } + control._userSelecting = false; ]]> diff --git a/toolkit/content/widgets/richlistbox.xml b/toolkit/content/widgets/richlistbox.xml index 47b8be5f316c..75a4f4a3f998 100644 --- a/toolkit/content/widgets/richlistbox.xml +++ b/toolkit/content/widgets/richlistbox.xml @@ -265,19 +265,20 @@ // (including the currently selected one), and determine // the index of the first one lying (partially) outside var height = this.scrollBoxObject.height; - var border = this.currentItem.boxObject.y; + var startBorder = this.currentItem.boxObject.y; if (aDirection == -1) - border += this.currentItem.boxObject.height; + startBorder += this.currentItem.boxObject.height; + var index = this.currentIndex; - while (0 <= index && index < children.length) { - var border2 = children[index].boxObject.y; - if (aDirection == -1) - border2 += children[index].boxObject.height; - if ((border2 - border) * aDirection > height) - break; - index += aDirection; + for (var ix = index; 0 <= ix && ix < children.length; ix += aDirection) { + var boxObject = children[ix].boxObject; + if (boxObject.height == 0) + continue; // hidden children have a y of 0 + var endBorder = boxObject.y + (aDirection == -1 ? boxObject.height : 0); + if ((endBorder - startBorder) * aDirection > height) + break; // we've reached the desired distance + index = ix; } - index -= aDirection; return index != this.currentIndex ? index - this.currentIndex : aDirection; ]]> diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css index fb7d27691ee2..8bd0d9dc80a6 100644 --- a/toolkit/content/xul.css +++ b/toolkit/content/xul.css @@ -149,10 +149,6 @@ browser { -moz-binding: url("chrome://global/content/bindings/browser.xml#browser"); } -tabbrowser { - -moz-binding: url("chrome://global/content/bindings/tabbrowser.xml#tabbrowser"); -} - editor { -moz-binding: url("chrome://global/content/bindings/editor.xml#editor"); } @@ -617,45 +613,11 @@ tab { -moz-box-pack: center; } -.tabbrowser-tabs { - -moz-binding: url("chrome://global/content/bindings/tabbrowser.xml#tabbrowser-tabs"); -} - -.tabbrowser-arrowscrollbox { - -moz-binding: url("chrome://global/content/bindings/tabbrowser.xml#tabbrowser-arrowscrollbox"); -} - -.tabbrowser-tabs > .tabbrowser-tab { - -moz-binding: url("chrome://global/content/bindings/tabbrowser.xml#tabbrowser-tab"); -} - -.tabbrowser-tabs > .tabbrowser-tab > .tab-close-button, -.tabbrowser-tabs .tabs-closebutton-box > .tabs-closebutton { - -moz-binding: url("chrome://global/content/bindings/tabbrowser.xml#tabbrowser-close-tab-button"); -} - -.tab-close-button { - display: none; -} - -.tabbrowser-tabs:not([closebuttons="noclose"]):not([closebuttons="closeatend"]) > .tabbrowser-tab[selected="true"] > .tab-close-button { - display: -moz-box; -} - -.tabbrowser-tabs[closebuttons="alltabs"] > .tabbrowser-tab > .tab-close-button { - display: -moz-box; -} - tabpanels { -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tabpanels"); display: -moz-deck; } -.tabs-alltabs-popup { - -moz-binding: url("chrome://global/content/bindings/tabbrowser.xml#tabbrowser-alltabs-popup"); -} - - /********** progressmeter **********/ progressmeter { diff --git a/toolkit/crashreporter/tools/symbolstore.py b/toolkit/crashreporter/tools/symbolstore.py index 2398be2bd369..94bc55c1ff5e 100755 --- a/toolkit/crashreporter/tools/symbolstore.py +++ b/toolkit/crashreporter/tools/symbolstore.py @@ -113,13 +113,17 @@ def GetVCSFilename(file, srcdir): if srcdir is not None: # strip the base path off # but we actually want the last dir in srcdir - # also, we want forward slashes on win32 paths file = os.path.normpath(file) - file = file.replace(srcdir, "", 1) + # the lower() is to handle win32+vc8, where + # the source filenames come out all lowercase, + # but the srcdir can be mixed case + if file.lower().startswith(srcdir.lower()): + file = file[len(srcdir):] (head, tail) = os.path.split(srcdir) if tail == "": tail = os.path.basename(head) file = tail + file + # we want forward slashes on win32 paths file = file.replace("\\", "/") return "cvs:%s:%s:%s" % (root, file, rev) file = file.replace("\\", "/") diff --git a/toolkit/library/libxul-config.mk b/toolkit/library/libxul-config.mk index ee51be726d4d..9641cf74ac41 100644 --- a/toolkit/library/libxul-config.mk +++ b/toolkit/library/libxul-config.mk @@ -114,7 +114,6 @@ COMPONENT_LIBS += \ htmlpars \ imglib2 \ gklayout \ - xmlextras \ docshell \ embedcomponents \ webbrwsr \ @@ -127,6 +126,12 @@ COMPONENT_LIBS += \ pipnss \ $(NULL) +ifdef MOZ_XMLEXTRAS +COMPONENT_LIBS += \ + xmlextras \ + $(NULL) +endif + ifdef MOZ_PLUGINS DEFINES += -DMOZ_PLUGINS COMPONENT_LIBS += \ diff --git a/toolkit/library/nsStaticXULComponents.cpp b/toolkit/library/nsStaticXULComponents.cpp index 3ff403fcc02a..bf5aa94af897 100644 --- a/toolkit/library/nsStaticXULComponents.cpp +++ b/toolkit/library/nsStaticXULComponents.cpp @@ -250,6 +250,12 @@ #define SPELLCHECK_MODULE #endif +#ifdef MOZ_XMLEXTRAS +#define XMLEXTRAS_MODULE MODULE(nsXMLExtrasModule) +#else +#define XMLEXTRAS_MODULE +#endif + #define XUL_MODULES \ MODULE(xpconnect) \ MATHML_MODULES \ @@ -273,7 +279,6 @@ ICON_MODULE \ PLUGINS_MODULES \ MODULE(nsLayoutModule) \ - MODULE(nsXMLExtrasModule) \ WEBSERVICES_MODULES \ MODULE(docshell_provider) \ MODULE(embedcomponents) \ @@ -299,6 +304,7 @@ MODULE(NSS) \ SYSTEMPREF_MODULES \ SPELLCHECK_MODULE \ + XMLEXTRAS_MODULE \ LAYOUT_DEBUG_MODULE \ /* end of list */ diff --git a/toolkit/locales/en-US/chrome/global/browser.properties b/toolkit/locales/en-US/chrome/global/browser.properties new file mode 100644 index 000000000000..d2372fe4f5d6 --- /dev/null +++ b/toolkit/locales/en-US/chrome/global/browser.properties @@ -0,0 +1,4 @@ +browsewithcaret.checkMsg=Do not show me this dialog box again. +browsewithcaret.checkWindowTitle=Caret Browsing +browsewithcaret.checkLabel=Pressing F7 turns Caret Browsing on or off. This feature places a moveable cursor in web pages, allowing you to select text with the keyboard. Do you want to turn Caret Browsing on? +browsewithcaret.checkButtonLabel=Yes diff --git a/toolkit/locales/en-US/chrome/mozapps/downloads/downloads.properties b/toolkit/locales/en-US/chrome/mozapps/downloads/downloads.properties index 3f5aa37e2ba5..0e9aea6f435f 100644 --- a/toolkit/locales/en-US/chrome/mozapps/downloads/downloads.properties +++ b/toolkit/locales/en-US/chrome/mozapps/downloads/downloads.properties @@ -60,3 +60,6 @@ fileExecutableSecurityWarningTitle=Open Executable File? fileExecutableSecurityWarningDontAsk=Don't ask me this again displayNameDesktop=Desktop + +# Desktop folder name for downloaded files +downloadsFolder=Downloads diff --git a/toolkit/locales/en-US/chrome/mozapps/downloads/unknownContentType.properties b/toolkit/locales/en-US/chrome/mozapps/downloads/unknownContentType.properties index 2ec9767394e6..6853efe2618e 100755 --- a/toolkit/locales/en-US/chrome/mozapps/downloads/unknownContentType.properties +++ b/toolkit/locales/en-US/chrome/mozapps/downloads/unknownContentType.properties @@ -43,7 +43,6 @@ chooseAppFilePickerTitle=Choose Helper Application badApp=The application you chose ("%S") could not be found. Check the file name or choose another application. badApp.title=Application not found selectDownloadDir=Select Download Folder -myDownloads=My Downloads unknownAccept.label=Save File unknownCancel.label=Cancel fileType=%S file diff --git a/toolkit/locales/jar.mn b/toolkit/locales/jar.mn index 7881a4d510d4..9c36bbc3fb8b 100644 --- a/toolkit/locales/jar.mn +++ b/toolkit/locales/jar.mn @@ -4,6 +4,7 @@ % locale global @AB_CD@ %locale/@AB_CD@/global/ locale/@AB_CD@/global/about.dtd (%chrome/global/about.dtd) locale/@AB_CD@/global/brand.dtd (generic/chrome/global/brand.dtd) ++ locale/@AB_CD@/global/browser.properties (%chrome/global/browser.properties) + locale/@AB_CD@/global/charsetOverlay.dtd (%chrome/global/charsetOverlay.dtd) + locale/@AB_CD@/global/commonDialog.dtd (%chrome/global/commonDialog.dtd) + locale/@AB_CD@/global/commonDialogs.properties (%chrome/global/commonDialogs.properties) @@ -41,8 +42,6 @@ + locale/@AB_CD@/global/printProgress.dtd (%chrome/global/printProgress.dtd) + locale/@AB_CD@/global/regionNames.properties (%chrome/global/regionNames.properties) + locale/@AB_CD@/global/dialog.properties (%chrome/global/dialog.properties) -+ locale/@AB_CD@/global/tabbrowser.dtd (%chrome/global/tabbrowser.dtd) -+ locale/@AB_CD@/global/tabbrowser.properties (%chrome/global/tabbrowser.properties) + locale/@AB_CD@/global/tree.dtd (%chrome/global/tree.dtd) + locale/@AB_CD@/global/textcontext.dtd (%chrome/global/textcontext.dtd) + locale/@AB_CD@/global/viewSource.dtd (%chrome/global/viewSource.dtd) diff --git a/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in b/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in index a400b1b902ee..f4c18066ad8a 100644 --- a/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in +++ b/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in @@ -87,26 +87,37 @@ nsUnknownContentTypeDialog.prototype = { show: function(aLauncher, aContext, aReason) { this.mLauncher = aLauncher; this.mContext = aContext; - // Display the dialog using the Window Watcher interface. - - var ir = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor); - var dwi = ir.getInterface(Components.interfaces.nsIDOMWindowInternal); - var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] - .getService(Components.interfaces.nsIWindowWatcher); - this.mDialog = ww.openWindow(dwi, - "chrome://mozapps/content/downloads/unknownContentType.xul", - null, - "chrome,centerscreen,titlebar,dialog=yes,dependent", - null); - // Hook this object to the dialog. - this.mDialog.dialog = this; - - // Hook up utility functions. - this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey; - - // Watch for error notifications. - this.progressListener.helperAppDlg = this; - this.mLauncher.setWebProgressListener(this.progressListener); + + const nsITimer = Components.interfaces.nsITimer; + this._timer = Components.classes["@mozilla.org/timer;1"] + .createInstance(nsITimer); + this._timer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT); + }, + + // When opening from new tab, if tab closes while dialog is opening, + // (which is a race condition on the XUL file being cached and the timer + // in nsExternalHelperAppService), the dialog gets a blur and doesn't + // activate the OK button. So we wait a bit before doing opening it. + reallyShow: function() { + var ir = this.mContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor); + var dwi = ir.getInterface(Components.interfaces.nsIDOMWindowInternal); + var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] + .getService(Components.interfaces.nsIWindowWatcher); + this.mDialog = ww.openWindow(dwi, + "chrome://mozapps/content/downloads/unknownContentType.xul", + null, + "chrome,centerscreen,titlebar,dialog=yes,dependent", + null); + + // Hook this object to the dialog. + this.mDialog.dialog = this; + + // Hook up utility functions. + this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey; + + // Watch for error notifications. + this.progressListener.helperAppDlg = this; + this.mLauncher.setWebProgressListener(this.progressListener); }, // promptForSaveToFile: Display file picker dialog and return selected file. @@ -121,134 +132,86 @@ nsUnknownContentTypeDialog.prototype = { // Note - this function is called without a dialog, so it cannot access any part // of the dialog XUL as other functions on this object do. promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension) { - var result = ""; + var result = null; this.mLauncher = aLauncher; - // If the user is always downloading to the same location, the default download - // folder is stored in preferences. If a value is found stored, use that - // automatically and don't ask via a dialog. + // Check to see if the user wishes to auto save to the default download + // folder without prompting. var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch); var autodownload = prefs.getBoolPref("browser.download.useDownloadDir"); + if (autodownload) { - function getSpecialFolderKey(aFolderType) - { - if (aFolderType == "Desktop") - return "Desk"; - - if (aFolderType != "Downloads") - throw "ASSERTION FAILED: folder type should be 'Desktop' or 'Downloads'"; - -#ifdef XP_WIN - return "Pers"; -#else -#ifdef XP_MACOSX - return "UsrDocs"; -#else - return "Home"; -#endif -#endif - } - - function getDownloadsFolder(aFolder) - { - var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties); - - var dir = fileLocator.get(getSpecialFolderKey(aFolder), Components.interfaces.nsILocalFile); - - var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService); - bundle = bundle.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties"); - - var description = bundle.GetStringFromName("myDownloads"); - if (aFolder != "Desktop") - dir.append(description); - - return dir; - } - - var defaultFolder = null; - switch (prefs.getIntPref("browser.download.folderList")) { - case 0: - defaultFolder = getDownloadsFolder("Desktop"); - break; - case 1: - defaultFolder = getDownloadsFolder("Downloads"); - break; - case 2: - defaultFolder = prefs.getComplexValue("browser.download.dir", Components.interfaces.nsILocalFile); - break; - } - + // Retrieve the user's default download directory + var dnldMgr = Components.classes["@mozilla.org/download-manager;1"] + .getService(Components.interfaces.nsIDownloadManager); + var defaultFolder = dnldMgr.userDownloadsDirectory; result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension); } - if (!result) { - // Use file picker to show dialog. - var nsIFilePicker = Components.interfaces.nsIFilePicker; - var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); + // Check to make sure we have a valid directory, otherwise, prompt + if (result) + return result; + + // Use file picker to show dialog. + var nsIFilePicker = Components.interfaces.nsIFilePicker; + var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); - var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService); - bundle = bundle.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties"); + var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService); + bundle = bundle.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties"); - var windowTitle = bundle.GetStringFromName("saveDialogTitle"); - var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowInternal); - picker.init(parent, windowTitle, nsIFilePicker.modeSave); - picker.defaultString = aDefaultFile; + var windowTitle = bundle.GetStringFromName("saveDialogTitle"); + var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowInternal); + picker.init(parent, windowTitle, nsIFilePicker.modeSave); + picker.defaultString = aDefaultFile; - if (aSuggestedFileExtension) { - // aSuggestedFileExtension includes the period, so strip it - picker.defaultExtension = aSuggestedFileExtension.substring(1); - } - else { - try { - picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension; - } - catch (ex) { } - } - - var wildCardExtension = "*"; - if (aSuggestedFileExtension) { - wildCardExtension += aSuggestedFileExtension; - picker.appendFilter(this.mLauncher.MIMEInfo.description, wildCardExtension); - } - - picker.appendFilters( nsIFilePicker.filterAll ); - - // Pull in the user's preferences and get the default download directory. - var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch); + if (aSuggestedFileExtension) { + // aSuggestedFileExtension includes the period, so strip it + picker.defaultExtension = aSuggestedFileExtension.substring(1); + } + else { try { - var startDir = prefs.getComplexValue("browser.download.dir", Components.interfaces.nsILocalFile); - if (startDir.exists()) { - picker.displayDirectory = startDir; - } + picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension; } - catch(exception) { } + catch (ex) { } + } - var dlgResult = picker.show(); + var wildCardExtension = "*"; + if (aSuggestedFileExtension) { + wildCardExtension += aSuggestedFileExtension; + picker.appendFilter(this.mLauncher.MIMEInfo.description, wildCardExtension); + } - if (dlgResult == nsIFilePicker.returnCancel) { - // null result means user cancelled. - return null; - } - - - // Be sure to save the directory the user chose through the Save As... - // dialog as the new browser.download.dir - result = picker.file; - - if (result) { - try { - // Remove the file so that it's not there when we ensure non-existence later; - // this is safe because for the file to exist, the user would have had to - // confirm that he wanted the file overwritten. - if (result.exists()) - result.remove(false); - } - catch (e) { } - var newDir = result.parent; - prefs.setComplexValue("browser.download.dir", Components.interfaces.nsILocalFile, newDir); - result = this.validateLeafName(newDir, result.leafName, null); + picker.appendFilters( nsIFilePicker.filterAll ); + + // Get the default download directory from download manager + var dnldMgr = Components.classes["@mozilla.org/download-manager;1"] + .getService(Components.interfaces.nsIDownloadManager); + var startDir = dnldMgr.defaultDownloadsDirectory; + picker.displayDirectory = startDir; + + if (picker.show() == nsIFilePicker.returnCancel) { + // null result means user cancelled. + return null; + } + + // Be sure to save the directory the user chose through the Save As... + // dialog as the new browser.download.dir since the old one + // didn't exist. + result = picker.file; + + if (result) { + try { + // Remove the file so that it's not there when we ensure non-existence later; + // this is safe because for the file to exist, the user would have had to + // confirm that he wanted the file overwritten. + if (result.exists()) + result.remove(false); } + catch (e) { } + var newDir = result.parent; + prefs.setComplexValue("browser.download.dir", Components.interfaces.nsILocalFile, newDir); + result = this.validateLeafName(newDir, result.leafName, null); } return result; }, @@ -502,17 +465,24 @@ nsUnknownContentTypeDialog.prototype = { .createInstance(nsITimer); this._timer.initWithCallback(this, 250, nsITimer.TYPE_ONE_SHOT); }, - + _timer: null, notify: function (aTimer) { - try { // The user may have already canceled the dialog. - if (!this._blurred) - this.mDialog.document.documentElement.getButton('accept').disabled = false; - } catch (ex) {} - this._delayExpired = true; - this._timer = null; // the timer won't release us, so we have to release it + if (!this.mDialog) { + this.reallyShow(); + } else { + // The user may have already canceled the dialog. + try { + if (!this._blurred) { + this.mDialog.document.documentElement.getButton("accept").disabled = false; + } + } catch (ex) {} + this._delayExpired = true; + } + // The timer won't release us, so we have to release it. + this._timer = null; }, - + postShowCallback: function () { this.mDialog.sizeToContent(); diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js index 08d72736de4f..744b9fe2707b 100644 --- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -300,8 +300,10 @@ function showView(aView) { showInstallFile = false; showCheckUpdatesAll = false; showInstallUpdatesAll = true; - if (gUpdatesOnly) + if (gUpdatesOnly) { showSkip = true; + showRestartApp = false; + } bindingList = [ ["aboutURL", "?aboutURL"], ["availableUpdateURL", "?availableUpdateURL"], ["availableUpdateVersion", "?availableUpdateVersion"], diff --git a/toolkit/mozapps/extensions/content/extensions.xul b/toolkit/mozapps/extensions/content/extensions.xul index d744087c38ff..eeaca243cdd3 100644 --- a/toolkit/mozapps/extensions/content/extensions.xul +++ b/toolkit/mozapps/extensions/content/extensions.xul @@ -208,10 +208,6 @@ tooltiptextaddons="&cmd.checkUpdatesAllAddon.tooltip;" tooltiptextthemes="&cmd.checkUpdatesAllTheme.tooltip;" command="cmd_checkUpdatesAll"/> -