From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/suite/browser/SuiteBrowser.manifest | 23 + comm/suite/browser/browser-places.js | 983 ++++++ comm/suite/browser/content.js | 901 +++++ comm/suite/browser/fullScreen.js | 107 + comm/suite/browser/hiddenWindow.xul | 55 + comm/suite/browser/jar.mn | 41 + comm/suite/browser/linkToolbarHandler.js | 295 ++ comm/suite/browser/linkToolbarItem.js | 215 ++ comm/suite/browser/linkToolbarOverlay.js | 213 ++ comm/suite/browser/linkToolbarOverlay.xul | 165 + comm/suite/browser/mailNavigatorOverlay.js | 176 + comm/suite/browser/mailNavigatorOverlay.xul | 96 + comm/suite/browser/metadata.js | 526 +++ comm/suite/browser/metadata.xul | 192 + comm/suite/browser/moz.build | 22 + comm/suite/browser/navigator.css | 135 + comm/suite/browser/navigator.js | 3311 +++++++++++++++++ comm/suite/browser/navigator.xul | 571 +++ comm/suite/browser/navigatorDD.js | 121 + comm/suite/browser/navigatorOverlay.xul | 716 ++++ comm/suite/browser/nsBrowserContentHandler.js | 634 ++++ comm/suite/browser/nsBrowserContentListener.js | 138 + comm/suite/browser/nsBrowserStatusHandler.js | 473 +++ comm/suite/browser/nsTypeAheadFind.js | 416 +++ comm/suite/browser/pageinfo/feeds.js | 31 + comm/suite/browser/pageinfo/feeds.xml | 40 + comm/suite/browser/pageinfo/pageInfo.css | 18 + comm/suite/browser/pageinfo/pageInfo.js | 1177 +++++++ comm/suite/browser/pageinfo/pageInfo.xul | 531 +++ comm/suite/browser/pageinfo/permissions.js | 204 ++ comm/suite/browser/pageinfo/security.js | 354 ++ comm/suite/browser/safeBrowsingOverlay.js | 75 + comm/suite/browser/safeBrowsingOverlay.xul | 32 + comm/suite/browser/sessionHistoryUI.js | 172 + comm/suite/browser/tabbrowser.xml | 3707 ++++++++++++++++++++ .../browser/test/browser/alltabslistener.html | 8 + comm/suite/browser/test/browser/authenticate.sjs | 210 ++ comm/suite/browser/test/browser/browser.ini | 38 + .../test/browser/browser_alltabslistener.js | 201 ++ .../browser/test/browser/browser_bug329212.js | 42 + .../browser/test/browser/browser_bug409624.js | 57 + .../browser/test/browser/browser_bug413915.js | 70 + .../browser/test/browser/browser_bug427559.js | 39 + .../browser/test/browser/browser_bug435325.js | 56 + .../browser/test/browser/browser_bug462289.js | 87 + .../browser/test/browser/browser_bug519216.js | 50 + .../browser/test/browser/browser_bug561636.js | 459 +++ .../browser/test/browser/browser_bug562649.js | 26 + .../browser/test/browser/browser_bug581947.js | 95 + .../browser/test/browser/browser_bug585511.js | 24 + .../browser/test/browser/browser_bug595507.js | 39 + .../browser/test/browser/browser_bug623155.js | 136 + comm/suite/browser/test/browser/browser_ctrlTab.js | 197 ++ comm/suite/browser/test/browser/browser_fayt.js | 25 + .../browser/browser_notification_tab_switching.js | 95 + .../suite/browser/test/browser/browser_pageInfo.js | 38 + .../test/browser/browser_page_style_menu.js | 67 + .../test/browser/browser_popupNotification.js | 782 +++++ .../browser_privatebrowsing_protocolhandler.js | 65 + ...owser_privatebrowsing_protocolhandler_page.html | 13 + .../browser/test/browser/browser_relatedTabs.js | 52 + comm/suite/browser/test/browser/browser_scope.js | 4 + .../test/browser/browser_selectTabAtIndex.js | 22 + .../browser/test/browser/browser_urlbarCopying.js | 204 ++ comm/suite/browser/test/browser/feed_tab.html | 17 + .../test/browser/file_dom_notifications.html | 40 + comm/suite/browser/test/browser/head.js | 63 + .../browser/test/browser/page_style_sample.html | 31 + .../browser/test/browser/redirect_bug623155.sjs | 16 + comm/suite/browser/test/browser/title_test.svg | 59 + comm/suite/browser/test/chrome/chrome.ini | 3 + .../browser/test/chrome/test_maxSniffing.html | 37 + comm/suite/browser/test/mochitest/audio.ogg | Bin 0 -> 47411 bytes .../browser/test/mochitest/bug364677-data.xml | 5 + .../test/mochitest/bug364677-data.xml^headers^ | 1 + .../browser/test/mochitest/bug395533-data.txt | 6 + .../browser/test/mochitest/bug436801-data.xml | 44 + .../suite/browser/test/mochitest/ctxmenu-image.png | Bin 0 -> 5401 bytes .../browser/test/mochitest/feed_discovery.html | 112 + comm/suite/browser/test/mochitest/mochitest.ini | 17 + .../browser/test/mochitest/subtst_contextmenu.html | 70 + .../browser/test/mochitest/test_bug364677.html | 32 + .../browser/test/mochitest/test_bug395533.html | 39 + .../browser/test/mochitest/test_bug436801.html | 118 + .../browser/test/mochitest/test_contextmenu.html | 931 +++++ .../test/mochitest/test_feed_discovery.html | 56 + .../test/mochitest/test_registerHandler.html | 85 + comm/suite/browser/test/mochitest/valid-feed.xml | 23 + .../test/mochitest/valid-unsniffable-feed.xml | 32 + comm/suite/browser/test/mochitest/video.ogg | Bin 0 -> 285310 bytes comm/suite/browser/urlbarBindings.xml | 694 ++++ comm/suite/browser/webDeveloperOverlay.js | 197 ++ comm/suite/browser/webDeveloperOverlay.xul | 156 + 93 files changed, 22951 insertions(+) create mode 100644 comm/suite/browser/SuiteBrowser.manifest create mode 100644 comm/suite/browser/browser-places.js create mode 100644 comm/suite/browser/content.js create mode 100644 comm/suite/browser/fullScreen.js create mode 100644 comm/suite/browser/hiddenWindow.xul create mode 100644 comm/suite/browser/jar.mn create mode 100644 comm/suite/browser/linkToolbarHandler.js create mode 100644 comm/suite/browser/linkToolbarItem.js create mode 100644 comm/suite/browser/linkToolbarOverlay.js create mode 100644 comm/suite/browser/linkToolbarOverlay.xul create mode 100644 comm/suite/browser/mailNavigatorOverlay.js create mode 100644 comm/suite/browser/mailNavigatorOverlay.xul create mode 100644 comm/suite/browser/metadata.js create mode 100644 comm/suite/browser/metadata.xul create mode 100644 comm/suite/browser/moz.build create mode 100644 comm/suite/browser/navigator.css create mode 100644 comm/suite/browser/navigator.js create mode 100644 comm/suite/browser/navigator.xul create mode 100644 comm/suite/browser/navigatorDD.js create mode 100644 comm/suite/browser/navigatorOverlay.xul create mode 100644 comm/suite/browser/nsBrowserContentHandler.js create mode 100644 comm/suite/browser/nsBrowserContentListener.js create mode 100644 comm/suite/browser/nsBrowserStatusHandler.js create mode 100644 comm/suite/browser/nsTypeAheadFind.js create mode 100644 comm/suite/browser/pageinfo/feeds.js create mode 100644 comm/suite/browser/pageinfo/feeds.xml create mode 100644 comm/suite/browser/pageinfo/pageInfo.css create mode 100644 comm/suite/browser/pageinfo/pageInfo.js create mode 100644 comm/suite/browser/pageinfo/pageInfo.xul create mode 100644 comm/suite/browser/pageinfo/permissions.js create mode 100644 comm/suite/browser/pageinfo/security.js create mode 100644 comm/suite/browser/safeBrowsingOverlay.js create mode 100644 comm/suite/browser/safeBrowsingOverlay.xul create mode 100644 comm/suite/browser/sessionHistoryUI.js create mode 100644 comm/suite/browser/tabbrowser.xml create mode 100644 comm/suite/browser/test/browser/alltabslistener.html create mode 100644 comm/suite/browser/test/browser/authenticate.sjs create mode 100644 comm/suite/browser/test/browser/browser.ini create mode 100644 comm/suite/browser/test/browser/browser_alltabslistener.js create mode 100644 comm/suite/browser/test/browser/browser_bug329212.js create mode 100644 comm/suite/browser/test/browser/browser_bug409624.js create mode 100644 comm/suite/browser/test/browser/browser_bug413915.js create mode 100644 comm/suite/browser/test/browser/browser_bug427559.js create mode 100644 comm/suite/browser/test/browser/browser_bug435325.js create mode 100644 comm/suite/browser/test/browser/browser_bug462289.js create mode 100644 comm/suite/browser/test/browser/browser_bug519216.js create mode 100644 comm/suite/browser/test/browser/browser_bug561636.js create mode 100644 comm/suite/browser/test/browser/browser_bug562649.js create mode 100644 comm/suite/browser/test/browser/browser_bug581947.js create mode 100644 comm/suite/browser/test/browser/browser_bug585511.js create mode 100644 comm/suite/browser/test/browser/browser_bug595507.js create mode 100644 comm/suite/browser/test/browser/browser_bug623155.js create mode 100644 comm/suite/browser/test/browser/browser_ctrlTab.js create mode 100644 comm/suite/browser/test/browser/browser_fayt.js create mode 100644 comm/suite/browser/test/browser/browser_notification_tab_switching.js create mode 100644 comm/suite/browser/test/browser/browser_pageInfo.js create mode 100644 comm/suite/browser/test/browser/browser_page_style_menu.js create mode 100644 comm/suite/browser/test/browser/browser_popupNotification.js create mode 100644 comm/suite/browser/test/browser/browser_privatebrowsing_protocolhandler.js create mode 100644 comm/suite/browser/test/browser/browser_privatebrowsing_protocolhandler_page.html create mode 100644 comm/suite/browser/test/browser/browser_relatedTabs.js create mode 100644 comm/suite/browser/test/browser/browser_scope.js create mode 100644 comm/suite/browser/test/browser/browser_selectTabAtIndex.js create mode 100644 comm/suite/browser/test/browser/browser_urlbarCopying.js create mode 100644 comm/suite/browser/test/browser/feed_tab.html create mode 100644 comm/suite/browser/test/browser/file_dom_notifications.html create mode 100644 comm/suite/browser/test/browser/head.js create mode 100644 comm/suite/browser/test/browser/page_style_sample.html create mode 100644 comm/suite/browser/test/browser/redirect_bug623155.sjs create mode 100644 comm/suite/browser/test/browser/title_test.svg create mode 100644 comm/suite/browser/test/chrome/chrome.ini create mode 100644 comm/suite/browser/test/chrome/test_maxSniffing.html create mode 100644 comm/suite/browser/test/mochitest/audio.ogg create mode 100644 comm/suite/browser/test/mochitest/bug364677-data.xml create mode 100644 comm/suite/browser/test/mochitest/bug364677-data.xml^headers^ create mode 100644 comm/suite/browser/test/mochitest/bug395533-data.txt create mode 100644 comm/suite/browser/test/mochitest/bug436801-data.xml create mode 100644 comm/suite/browser/test/mochitest/ctxmenu-image.png create mode 100644 comm/suite/browser/test/mochitest/feed_discovery.html create mode 100644 comm/suite/browser/test/mochitest/mochitest.ini create mode 100644 comm/suite/browser/test/mochitest/subtst_contextmenu.html create mode 100644 comm/suite/browser/test/mochitest/test_bug364677.html create mode 100644 comm/suite/browser/test/mochitest/test_bug395533.html create mode 100644 comm/suite/browser/test/mochitest/test_bug436801.html create mode 100644 comm/suite/browser/test/mochitest/test_contextmenu.html create mode 100644 comm/suite/browser/test/mochitest/test_feed_discovery.html create mode 100644 comm/suite/browser/test/mochitest/test_registerHandler.html create mode 100644 comm/suite/browser/test/mochitest/valid-feed.xml create mode 100644 comm/suite/browser/test/mochitest/valid-unsniffable-feed.xml create mode 100644 comm/suite/browser/test/mochitest/video.ogg create mode 100644 comm/suite/browser/urlbarBindings.xml create mode 100644 comm/suite/browser/webDeveloperOverlay.js create mode 100644 comm/suite/browser/webDeveloperOverlay.xul (limited to 'comm/suite/browser') diff --git a/comm/suite/browser/SuiteBrowser.manifest b/comm/suite/browser/SuiteBrowser.manifest new file mode 100644 index 0000000000..773ea9573c --- /dev/null +++ b/comm/suite/browser/SuiteBrowser.manifest @@ -0,0 +1,23 @@ +component {c2343730-dc2c-11d3-98b3-001083010e9b} nsBrowserContentHandler.js +contract @mozilla.org/uriloader/content-handler;1?type=text/html {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=application/vnd.mozilla.xul+xml {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=image/svg+xml {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=text/rdf {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=text/xml {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=application/xhtml+xml {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=text/css {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=text/plain {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=image/gif {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=image/jpeg {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=image/jpg {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=image/png {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=image/bmp {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=image/x-icon {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=image/vnd.microsoft.icon {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/uriloader/content-handler;1?type=application/http-index-format {c2343730-dc2c-11d3-98b3-001083010e9b} +contract @mozilla.org/commandlinehandler/general-startup;1?type=browser {c2343730-dc2c-11d3-98b3-001083010e9b} +category command-line-handler x-default @mozilla.org/commandlinehandler/general-startup;1?type=browser +category command-line-validator b-default @mozilla.org/commandlinehandler/general-startup;1?type=browser +component {45c8f75b-a299-4178-a461-f63690389055} nsTypeAheadFind.js +contract @mozilla.org/suite/typeaheadfind;1 {45c8f75b-a299-4178-a461-f63690389055} +category app-startup SuiteTypeAheadFind service,@mozilla.org/suite/typeaheadfind;1 diff --git a/comm/suite/browser/browser-places.js b/comm/suite/browser/browser-places.js new file mode 100644 index 0000000000..83584456e0 --- /dev/null +++ b/comm/suite/browser/browser-places.js @@ -0,0 +1,983 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyScriptGetter(this, ["PlacesToolbar", "PlacesMenu", + "PlacesPanelview", "PlacesPanelMenuView"], + "chrome://communicator/content/places/browserPlacesViews.js"); + +var StarUI = { + _itemGuids: null, + uri: null, + _batching: false, + + _element: function(aID) { + return document.getElementById(aID); + }, + + // Edit-bookmark panel + get panel() { + delete this.panel; + var element = this._element("editBookmarkPanel"); + // initially the panel is hidden + // to avoid impacting startup / new window performance + element.hidden = false; + element.addEventListener("popuphidden", this); + element.addEventListener("keypress", this); + element.addEventListener("keypress", this); + element.addEventListener("mousedown", this); + element.addEventListener("mouseout", this); + element.addEventListener("mousemove", this); + element.addEventListener("compositionstart", this); + element.addEventListener("compositionend", this); + element.addEventListener("input", this); + element.addEventListener("popuphidden", this); + element.addEventListener("popupshown", this); + return this.panel = element; + }, + + // Array of command elements to disable when the panel is opened. + get _blockedCommands() { + delete this._blockedCommands; + return this._blockedCommands = + ["cmd_close", "cmd_closeWindow"].map(id => this._element(id)); + }, + + _blockCommands: function SU__blockCommands() { + this._blockedCommands.forEach(function (elt) { + // make sure not to permanently disable this item (see bug 409155) + if (elt.hasAttribute("wasDisabled")) + return; + if (elt.getAttribute("disabled") == "true") { + elt.setAttribute("wasDisabled", "true"); + } else { + elt.setAttribute("wasDisabled", "false"); + elt.setAttribute("disabled", "true"); + } + }); + }, + + _restoreCommandsState: function SU__restoreCommandsState() { + this._blockedCommands.forEach(function (elt) { + if (elt.getAttribute("wasDisabled") != "true") + elt.removeAttribute("disabled"); + elt.removeAttribute("wasDisabled"); + }); + }, + + // nsIDOMEventListener + handleEvent: function SU_handleEvent(aEvent) { + switch (aEvent.type) { + case "popuphidden": + if (aEvent.originalTarget == this.panel) { + if (!this._element("editBookmarkPanelContent").hidden) + this.quitEditMode(); + + this._restoreCommandsState(); + let guidsForRemoval = this._itemGuids; + this._itemGuids = null; + + if (this._batching) { + this.endBatch(); + } + + switch (this._actionOnHide) { + case "cancel": { + PlacesTransactions.undo().catch(Cu.reportError); + break; + } + case "remove": { + PlacesTransactions.Remove(guidsForRemoval) + .transact().catch(Cu.reportError); + break; + } + } + this._actionOnHide = ""; + } + break; + case "keypress": + if (aEvent.defaultPrevented) { + // The event has already been consumed inside of the panel. + break; + } + switch (aEvent.keyCode) { + case KeyEvent.DOM_VK_ESCAPE: + if (!this._element("editBookmarkPanelContent").hidden) + this.cancelButtonOnCommand(); + break; + case KeyEvent.DOM_VK_RETURN: + if (aEvent.target.className == "expander-up" || + aEvent.target.className == "expander-down" || + aEvent.target.id == "editBMPanel_newFolderButton") { + //XXX Why is this necessary? The defaultPrevented check should + // be enough. + break; + } + this.panel.hidePopup(true); + break; + } + break; + } + }, + + _overlayLoaded: false, + _overlayLoading: false, + async showEditBookmarkPopup(aNode, aAnchorElement, aPosition, aIsNewBookmark, aUrl) { + // Slow double-clicks (not true double-clicks) shouldn't + // cause the panel to flicker. + if (this.panel.state == "showing" || + this.panel.state == "open") { + return; + } + + this._isNewBookmark = aIsNewBookmark; + this._uriForRemoval = ""; + this._itemGuids = null; + + // Performance: load the overlay the first time the panel is opened + // (see bug 392443). + if (this._overlayLoading) + return; + + if (this._overlayLoaded) { + await this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl); + return; + } + + this._overlayLoading = true; + document.loadOverlay( + "chrome://communicator/content/places/editBookmarkOverlay.xul", + (aSubject, aTopic, aData) => { + // Move the header (star, title, button) into the grid, + // so that it aligns nicely with the other items (bug 484022). + let header = this._element("editBookmarkPanelHeader"); + let rows = this._element("editBookmarkPanelGrid").lastChild; + rows.insertBefore(header, rows.firstChild); + header.hidden = false; + + this._overlayLoading = false; + this._overlayLoaded = true; + this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl); + } + ); + }, + + async _doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl) { + if (this.panel.state != "closed") + return; + + this._blockCommands(); // un-done in the popuphiding handler + + // Set panel title: + // if we are batching, i.e. the bookmark has been added now, + // then show Page Bookmarked, else if the bookmark did already exist, + // we are about editing it, then use Edit This Bookmark. + this._element("editBookmarkPanelTitle").value = + this._isNewBookmark ? + gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") : + gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle"); + + this._element("editBookmarkPanelBottomButtons").hidden = false; + this._element("editBookmarkPanelContent").hidden = false; + + // The label of the remove button differs if the URI is bookmarked + // multiple times. + this._itemGuids = []; + + await PlacesUtils.bookmarks.fetch({url: aUrl}, + bookmark => this._itemGuids.push(bookmark.guid)); + + let forms = gNavigatorBundle.getString("editBookmark.removeBookmarks.label"); + let bookmarksCount = this._itemGuids.length; + let label = PluralForm.get(bookmarksCount, forms) + .replace("#1", bookmarksCount); + this._element("editBookmarkPanelRemoveButton").label = label; + + this.beginBatch(); + + let onPanelReady = fn => { + let target = this.panel; + if (target.parentNode) { + // By targeting the panel's parent and using a capturing listener, we + // can have our listener called before others waiting for the panel to + // be shown (which probably expect the panel to be fully initialized) + target = target.parentNode; + } + target.addEventListener("popupshown", function(event) { + fn(); + }, {"capture": true, "once": true}); + }; + gEditItemOverlay.initPanel({ node: aNode, + onPanelReady, + hiddenRows: ["description", "location", + "loadInSidebar", "keyword"], + focusedElement: "preferred"}); + this.panel.openPopup(aAnchorElement, aPosition); + }, + + panelShown: + function SU_panelShown(aEvent) { + if (aEvent.target == this.panel) { + if (!this._element("editBookmarkPanelContent").hidden) { + let fieldToFocus = "editBMPanel_" + + Services.prefs.getCharPref("browser.bookmarks.editDialog.firstEditField"); + var elt = this._element(fieldToFocus); + elt.focus(); + elt.select(); + } + else { + // Note this isn't actually used anymore, we should remove this + // once we decide not to bring back the page bookmarked notification + this.panel.focus(); + } + } + }, + + quitEditMode: function SU_quitEditMode() { + this._element("editBookmarkPanelContent").hidden = true; + this._element("editBookmarkPanelBottomButtons").hidden = true; + gEditItemOverlay.uninitPanel(true); + }, + + editButtonCommand: function SU_editButtonCommand() { + this.showEditBookmarkPopup(); + }, + + cancelButtonOnCommand: function SU_cancelButtonOnCommand() { + this._actionOnHide = "cancel"; + this.panel.hidePopup(); + }, + + removeBookmarkButtonCommand: function SU_removeBookmarkButtonCommand() { + this._removeBookmarksOnPopupHidden = true; + this._actionOnHide = "remove"; + this.panel.hidePopup(); + }, + + _batchBlockingDeferred: null, + beginBatch() { + if (this._batching) + return; + this._batchBlockingDeferred = PromiseUtils.defer(); + PlacesTransactions.batch(async () => { + await this._batchBlockingDeferred.promise; + }); + this._batching = true; + }, + + endBatch() { + if (!this._batching) + return; + + this._batchBlockingDeferred.resolve(); + this._batchBlockingDeferred = null; + this._batching = false; + }, +}; + +var PlacesCommandHook = { + + /** + * Adds a bookmark to the page loaded in the given browser using the + * properties dialog. + * + * @param aBrowser + * a element. + * @param [optional] aShowEditUI + * whether or not to show the edit-bookmark UI for the bookmark item + * @param [optional] aUrl + * Option to provide a URL to bookmark rather than the current page + * @param [optional] aTitle + * Option to provide a title for a bookmark to use rather than the + * getting the current page's title + */ + async bookmarkPage(aBrowser, aShowEditUI, aUrl = null, aTitle = null) { + // If aUrl is provided, we want to bookmark that url rather than the + // the current page + let url = aUrl ? new URL(aUrl) : new URL(aBrowser.currentURI.spec); + let info = await PlacesUtils.bookmarks.fetch({ url }); + let isNewBookmark = !info; + if (!info) { + let parentGuid = PlacesUtils.bookmarks.unfiledGuid; + info = { url, parentGuid }; + let description = null; + let charset = null; + + let docInfo = aUrl ? {} : await this._getPageDetails(aBrowser); + + try { + if (docInfo.isErrorPage) { + let entry = await PlacesUtils.history.fetch(aBrowser.currentURI); + if (entry) { + info.title = entry.title; + } + } else { + info.title = aTitle || aBrowser.contentTitle; + } + info.title = info.title || url.href; + description = docInfo.description; + charset = aUrl ? null : aBrowser.characterSet; + } catch (e) { + Cu.reportError(e); + } + + if (aShowEditUI && isNewBookmark) { + // If we bookmark the page here but open right into a cancelable + // state (i.e. new bookmark in Library), start batching here so + // all of the actions can be undone in a single undo step. + StarUI.beginBatch(); + } + + if (description) { + info.annotations = [{ name: PlacesUIUtils.DESCRIPTION_ANNO, + value: description }]; + } + + info.guid = await PlacesTransactions.NewBookmark(info).transact(); + + // Set the character-set + if (charset && !PrivateBrowsingUtils.isBrowserPrivate(aBrowser)) + PlacesUtils.setCharsetForURI(makeURI(url.href), charset); + } + + // If it was not requested to open directly in "edit" mode, we are done. + if (!aShowEditUI) + return; + + let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(info); + + // Dock the panel to the star icon when possible, otherwise dock + // it to the content area. + if (aBrowser.contentWindow == window.content) { + let ubIcons = aBrowser.ownerDocument.getElementById("urlbar-icons"); + if (ubIcons) { + await StarUI.showEditBookmarkPopup(node, ubIcons, + "bottomcenter topright", + isNewBookmark, url); + return; + } + } + + await StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", + isNewBookmark, url); + }, + + _getPageDetails(browser) { + return new Promise(resolve => { + let mm = browser.messageManager; + mm.addMessageListener("Bookmarks:GetPageDetails:Result", function listener(msg) { + mm.removeMessageListener("Bookmarks:GetPageDetails:Result", listener); + resolve(msg.data); + }); + + mm.sendAsyncMessage("Bookmarks:GetPageDetails", { }); + }); + }, + + /** + * Adds a bookmark to the page targeted by a link. + * @param parentId + * The folder in which to create a new bookmark if aURL isn't + * bookmarked. + * @param url (string) + * the address of the link target + * @param title + * The link text + * @param [optional] description + * The linked page description, if available + */ + async bookmarkLink(parentId, url, title, description = "") { + let bm = await PlacesUtils.bookmarks.fetch({url}); + if (bm) { + let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm); + PlacesUIUtils.showBookmarkDialog({ action: "edit", node }, + window.top); + return; + } + + let parentGuid = parentId == PlacesUtils.bookmarksMenuFolderId ? + PlacesUtils.bookmarks.menuGuid : + await PlacesUtils.promiseItemGuid(parentId); + let defaultInsertionPoint = new PlacesInsertionPoint({ parentId, parentGuid }); + + PlacesUIUtils.showBookmarkDialog({ action: "add", + type: "bookmark", + uri: makeURI(url), + title, + description, + defaultInsertionPoint, + hiddenRows: [ "description", + "location", + "loadInSidebar", + "keyword" ] + }, window.top); + }, + + /** + * List of nsIURI objects characterizing the tabs currently open in the + * browser. The URIs will be in the order in which their + * corresponding tabs appeared and duplicates are discarded. + */ + get uniqueCurrentPages() { + let seenURIs = {}; + let URIs = []; + + gBrowser.tabs.forEach(tab => { + let browser = tab.linkedBrowser; + let uri = browser.currentURI; + // contentTitle is usually empty. + let title = browser.contentTitle || tab.label; + let spec = uri.spec; + if (!(spec in seenURIs)) { + // add to the set of seen URIs + seenURIs[uri.spec] = null; + URIs.push({ uri, title }); + } + }); + + return URIs; + }, + + /** + * Adds a folder with bookmarks to all of the currently open tabs in this + * window. + */ + bookmarkCurrentPages: function PCH_bookmarkCurrentPages() { + let pages = this.uniqueCurrentPages; + if (pages.length > 1) { + PlacesUIUtils.showBookmarkDialog({ action: "add", + type: "folder", + URIList: pages, + hiddenRows: [ "description" ] + }, window); + } + }, + + /** + * Updates disabled state for the "Bookmark All Tabs" command. + */ + updateBookmarkAllTabsCommand: + function PCH_updateBookmarkAllTabsCommand() { + // There's nothing to do in non-browser windows. + if (window.location.href != getBrowserURL()) + return; + + // Disable "Bookmark All Tabs" if there are less than two + // "unique current pages". + goSetCommandEnabled("Browser:BookmarkAllTabs", + this.uniqueCurrentPages.length >= 2); + }, + + /** + * Adds a Live Bookmark to a feed associated with the current page. + * @param url + * The nsIURI of the page the feed was attached to + * @title title + * The title of the feed. Optional. + * @subtitle subtitle + * A short description of the feed. Optional. + */ + async addLiveBookmark(url, feedTitle, feedSubtitle) { + let toolbarIP = new PlacesInsertionPoint({ + parentId: PlacesUtils.toolbarFolderId, + parentGuid: PlacesUtils.bookmarks.toolbarGuid + }); + + let feedURI = makeURI(url); + let title = feedTitle || gBrowser.contentTitle; + let description = feedSubtitle; + if (!description) { + description = (await this._getPageDetails(gBrowser.selectedBrowser)).description; + } + + PlacesUIUtils.showBookmarkDialog({ action: "add", + type: "livemark", + feedURI, + siteURI: gBrowser.currentURI, + title, + description, + defaultInsertionPoint: toolbarIP, + hiddenRows: [ "feedLocation", + "siteLocation", + "description" ] + }, window); + }, + + /** + * Opens the Places Organizer. + * @param {String} item The item to select in the organizer window, + * options are (case sensitive): + * BookmarksMenu, BookmarksToolbar, UnfiledBookmarks, + * AllBookmarks, History. + */ + showPlacesOrganizer(item) { + var organizer = Services.wm.getMostRecentWindow("Places:Organizer"); + // Due to bug 528706, getMostRecentWindow can return closed windows. + if (!organizer || organizer.closed) { + // No currently open places window, so open one with the specified mode. + openDialog("chrome://communicator/content/places/places.xul", + "", "chrome,toolbar=yes,dialog=no,resizable", item); + } else { + organizer.PlacesOrganizer.selectLeftPaneContainerByHierarchy(item); + organizer.focus(); + } + }, +}; + +/** + * Functions for handling events in the Bookmarks Toolbar and menu. + */ +var BookmarksEventHandler = { + + onMouseUp(aEvent) { + // Handles left-click with modifier if not browser.bookmarks.openInTabClosesMenu. + if (aEvent.button != 0 || PlacesUIUtils.openInTabClosesMenu) + return; + let target = aEvent.originalTarget; + if (target.tagName != "menuitem") + return; + let modifKey = AppConstants.platform === "macosx" ? aEvent.metaKey + : aEvent.ctrlKey; + // Don't keep menu open for 'Open all in Tabs'. + if (modifKey && !target.classList.contains("openintabs-menuitem")) { + target.setAttribute("closemenu", "none"); + } + }, + + /** + * Handler for click event for an item in the bookmarks toolbar or menu. + * Menus and submenus from the folder buttons bubble up to this handler. + * Left-click is handled in the onCommand function. + * When items are middle-clicked (or clicked with modifier), open in tabs. + * If the click came through a menu, close the menu. + * @param aEvent + * DOMEvent for the click + * @param aView + * The places view which aEvent should be associated with. + */ + onClick: function BEH_onClick(aEvent, aView) { + // Only handle middle-click or left-click with modifiers. + if (aEvent.button == 2 || (aEvent.button == 0 && !aEvent.shiftKey && + !aEvent.ctrlKey && !aEvent.metaKey)) + return; + + var target = aEvent.originalTarget; + // If this event bubbled up from a menu or menuitem, + // close the menus if browser.bookmarks.openInTabClosesMenu. + if ((PlacesUIUtils.openInTabClosesMenu && target.tagName == "menuitem") || + target.tagName == "menu" || + target.classList.contains("openintabs-menuitem")) { + closeMenus(aEvent.target); + } + // Command already precesssed so remove any closemenu attr set in onMouseUp. + if (aEvent.button == 0 && + target.tagName == "menuitem" && + target.getAttribute("closemenu") == "none") { + // On Mac we need to extend when we remove the flag, to avoid any pre-close + // animations. + setTimeout(() => { + target.removeAttribute("closemenu"); + }, 500); + } + + if (target._placesNode && PlacesUtils.nodeIsContainer(target._placesNode)) { + // Don't open the root folder in tabs when the empty area on the toolbar + // is middle-clicked or when a non-bookmark item except for Open in Tabs) + // in a bookmarks menupopup is middle-clicked. + if (target.localName == "menu" || target.localName == "toolbarbutton") + PlacesUIUtils.openContainerNodeInTabs(target._placesNode, aEvent, aView); + } + else if (aEvent.button == 1) { + // left-clicks with modifier are already served by onCommand + this.onCommand(aEvent); + } + }, + + /** + * Handler for command event for an item in the bookmarks toolbar. + * Menus and submenus from the folder buttons bubble up to this handler. + * Opens the item. + * @param aEvent + * DOMEvent for the command + */ + onCommand: function BEH_onCommand(aEvent) { + var target = aEvent.originalTarget; + if (target._placesNode) + PlacesUIUtils.openNodeWithEvent(target._placesNode, aEvent); + }, + + onPopupShowing: function BEH_onPopupShowing(aEvent) { + var browser = getBrowser(); + if (!aEvent.currentTarget.parentNode._placesView) + new PlacesMenu(aEvent, 'place:folder=BOOKMARKS_MENU'); + + document.getElementById("Browser:BookmarkAllTabs") + .setAttribute("disabled", !browser || browser.tabs.length == 1); + }, + + fillInBHTooltip: function BEH_fillInBHTooltip(aDocument, aEvent) { + var node; + var cropped = false; + var targetURI; + + if (aDocument.tooltipNode.localName == "treechildren") { + var tree = aDocument.tooltipNode.parentNode; + var tbo = tree.treeBoxObject; + var cell = tbo.getCellAt(aEvent.clientX, aEvent.clientY); + if (cell.row == -1) + return false; + node = tree.view.nodeForTreeIndex(cell.row); + cropped = tbo.isCellCropped(cell.row, cell.col); + } + else { + // Check whether the tooltipNode is a Places node. + // In such a case use it, otherwise check for targetURI attribute. + var tooltipNode = aDocument.tooltipNode; + if (tooltipNode._placesNode) + node = tooltipNode._placesNode; + else { + // This is a static non-Places node. + targetURI = tooltipNode.getAttribute("targetURI"); + } + } + + if (!node && !targetURI) + return false; + + // Show node.label as tooltip's title for non-Places nodes. + var title = node ? node.title : tooltipNode.label; + + // Show URL only for Places URI-nodes or nodes with a targetURI attribute. + var url; + if (targetURI || PlacesUtils.nodeIsURI(node)) + url = targetURI || node.uri; + + // Show tooltip for containers only if their title is cropped. + if (!cropped && !url) + return false; + + var tooltipTitle = aDocument.getElementById("bhtTitleText"); + tooltipTitle.hidden = (!title || (title == url)); + if (!tooltipTitle.hidden) + tooltipTitle.textContent = title; + + var tooltipUrl = aDocument.getElementById("bhtUrlText"); + tooltipUrl.hidden = !url; + if (!tooltipUrl.hidden) + tooltipUrl.value = url; + + // Show tooltip. + return true; + } +}; + + +// Handles special drag and drop functionality for Places menus that are not +// part of a Places view (e.g. the bookmarks menu in the menubar). +var PlacesMenuDNDHandler = { + _springLoadDelay: 350, // milliseconds + _loadTimer: null, + + /** + * Called when the user enters the element during a drag. + * @param event + * The DragEnter event that spawned the opening. + */ + onDragEnter: function PMDH_onDragEnter(event) { + // Opening menus in a Places popup is handled by the view itself. + if (!this._isStaticContainer(event.target)) + return; + + this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + this._loadTimer.initWithCallback(function() { + PlacesMenuDNDHandler._loadTimer = null; + event.target.lastChild.setAttribute("autoopened", "true"); + event.target.lastChild.showPopup(event.target.lastChild); + }, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT); + event.preventDefault(); + event.stopPropagation(); + }, + + /** + * Handles dragexit on the element. + * @returns true if the element is a container element (menu or + * menu-toolbarbutton), false otherwise. + */ + onDragExit: function PMDH_onDragExit(event) { + // Closing menus in a Places popup is handled by the view itself. + if (!this._isStaticContainer(event.target)) + return; + + if (this._loadTimer) { + this._loadTimer.cancel(); + this._loadTimer = null; + } + let closeTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + closeTimer.initWithCallback(function() { + let node = PlacesControllerDragHelper.currentDropTarget; + let inHierarchy = false; + while (node && !inHierarchy) { + inHierarchy = node == event.target; + node = node.parentNode; + } + if (!inHierarchy && event.target.lastChild && + event.target.lastChild.hasAttribute("autoopened")) { + event.target.lastChild.removeAttribute("autoopened"); + event.target.lastChild.hidePopup(); + } + }, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT); + }, + + /** + * Determines if a XUL element represents a static container. + * @returns true if the element is a container element (menu or + * menu-toolbarbutton), false otherwise. + */ + _isStaticContainer: function PMDH__isContainer(node) { + let isMenu = node.localName == "menu" || + (node.localName == "toolbarbutton" && + node.getAttribute("type") == "menu"); + let isStatic = !("_placesNode" in node) && node.lastChild && + node.lastChild.hasAttribute("placespopup") && + !node.parentNode.hasAttribute("placespopup"); + return isMenu && isStatic; + }, + + /** + * Called when the user drags over the element. + * @param event + * The DragOver event. + */ + onDragOver: function PMDH_onDragOver(event) { + let ip = new PlacesInsertionPoint({ + parentId: PlacesUtils.bookmarksMenuFolderId, + parentGuid: PlacesUtils.bookmarks.menuGuid + }); + if (ip && PlacesControllerDragHelper.canDrop(ip, event.dataTransfer)) + event.preventDefault(); + + event.stopPropagation(); + }, + + /** + * Called when the user drops on the element. + * @param event + * The Drop event. + */ + onDrop: function PMDH_onDrop(event) { + // Put the item at the end of bookmark menu. + let ip = new PlacesInsertionPoint({ + parentId: PlacesUtils.bookmarksMenuFolderId, + parentGuid: PlacesUtils.bookmarks.menuGuid + }); + PlacesControllerDragHelper.onDrop(ip, event.dataTransfer); + event.stopPropagation(); + } +}; + + +var BookmarkingUI = { + _hasBookmarksObserver: false, + _itemGuids: new Set(), + + uninit: function BUI_uninit() + { + if (this._hasBookmarksObserver) { + PlacesUtils.bookmarks.removeObserver(this); + } + + if (this._pendingUpdate) { + delete this._pendingUpdate; + } + }, + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsINavBookmarkObserver + ]), + + get _starredTooltip() + { + delete this._starredTooltip; + return this._starredTooltip = + gNavigatorBundle.getString("starButtonOn.tooltip"); + }, + + get _unstarredTooltip() + { + delete this._unstarredTooltip; + return this._unstarredTooltip = + gNavigatorBundle.getString("starButtonOff.tooltip"); + }, + + updateStarState: function BUI_updateStarState() { + this._uri = gBrowser.currentURI; + this._itemGuids = []; + let aItemGuids = []; + + // those objects are use to check if we are in the current iteration before + // returning any result. + let pendingUpdate = this._pendingUpdate = {}; + + PlacesUtils.bookmarks.fetch({url: this._uri}, b => aItemGuids.push(b.guid)) + .catch(Cu.reportError) + .then(() => { + if (pendingUpdate != this._pendingUpdate) { + return; + } + + // It's possible that onItemAdded gets called before the async statement + // calls back. For such an edge case, retain all unique entries from the + // array. + this._itemGuids = this._itemGuids.filter( + guid => !aItemGuids.includes(guid) + ).concat(aItemGuids); + + this._updateStar(); + + // Start observing bookmarks if needed. + if (!this._hasBookmarksObserver) { + try { + PlacesUtils.bookmarks.addObserver(this); + this._hasBookmarksObserver = true; + } catch (ex) { + Cu.reportError("BookmarkingUI failed adding a bookmarks observer: " + ex); + } + } + + delete this._pendingUpdate; + }); + }, + + _updateStar: function BUI__updateStar() + { + let starIcon = document.getElementById("star-button"); + if (this._itemGuids.length > 0) { + starIcon.setAttribute("starred", "true"); + starIcon.setAttribute("tooltiptext", this._starredTooltip); + } + else { + starIcon.removeAttribute("starred"); + starIcon.setAttribute("tooltiptext", this._unstarredTooltip); + } + }, + + onClick: function BUI_onClick(aEvent) + { + // Ignore clicks on the star while we update its state. + if (aEvent.button == 0 && !this._pendingUpdate) + PlacesCommandHook.bookmarkPage(gBrowser.selectedBrowser, + this._itemGuids.length > 0); + + }, + + // nsINavBookmarkObserver + onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded, aGuid) { + if (aURI && aURI.equals(this._uri)) { + // If a new bookmark has been added to the tracked uri, register it. + if (!this._itemGuids.includes(aGuid)) { + this._itemGuids.push(aGuid); + // Only need to update the UI if it wasn't marked as starred before: + if (this._itemGuids.length == 1) { + this._updateStar(); + } + } + } + }, + + onItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGuid) { + let index = this._itemGuids.indexOf(aGuid); + // If one of the tracked bookmarks has been removed, unregister it. + if (index != -1) { + this._itemGuids.splice(index, 1); + // Only need to update the UI if the page is no longer starred + if (this._itemGuids.length == 0) { + this._updateStar(); + } + } + }, + + onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue, aLastModified, + aItemType, aParentId, aGuid) { + if (aProperty == "uri") { + let index = this._itemGuids.indexOf(aGuid); + // If the changed bookmark was tracked, check if it is now pointing to + // a different uri and unregister it. + if (index != -1 && aNewValue != this._uri.spec) { + this._itemGuids.splice(index, 1); + // Only need to update the UI if the page is no longer starred + if (this._itemGuids.length == 0) { + this._updateStar(); + } + } else if (index == -1 && aNewValue == this._uri.spec) { + // If another bookmark is now pointing to the tracked uri, register it. + this._itemGuids.push(aGuid); + // Only need to update the UI if it wasn't marked as starred before: + if (this._itemGuids.length == 1) { + this._updateStar(); + } + } + } + }, + + onBeginUpdateBatch: function () {}, + onEndUpdateBatch: function () {}, + onItemVisited: function () {}, + onItemMoved: function () {} +}; + + +// This object handles the initialization and uninitialization of the bookmarks +// toolbar. updateStarState is called when the browser window is opened and +// after closing the toolbar customization dialog. +var PlacesToolbarHelper = { + _place: "place:folder=TOOLBAR", + get _viewElt() { + return document.getElementById("PlacesToolbar"); + }, + + init: function PTH_init() { + let viewElt = this._viewElt; + if (!viewElt || viewElt._placesView) + return; + + // There is no need to initialize the toolbar if customizing because + // init() will be called when the customization is done. + if (this._isCustomizing) + return; + + new PlacesToolbar(this._place); + }, + + customizeStart: function PTH_customizeStart() { + let viewElt = this._viewElt; + if (viewElt && viewElt._placesView) + viewElt._placesView.uninit(); + + this._isCustomizing = true; + }, + + customizeDone: function PTH_customizeDone() { + this._isCustomizing = false; + this.init(); + } +}; + + +// Handles the bookmarks menu popup +var BookmarksMenu = { + _popupInitialized: {}, + onPopupShowing: function BM_onPopupShowing(aEvent, aPrefix) { + if (!(aPrefix in this._popupInitialized)) { + // First popupshowing event, initialize immutable attributes. + this._popupInitialized[aPrefix] = true; + + // Need to set the label on Unsorted Bookmarks menu. + let unsortedBookmarksElt = + document.getElementById(aPrefix + "unsortedBookmarksFolderMenu"); + unsortedBookmarksElt.label = + PlacesUtils.getString("OtherBookmarksFolderTitle"); + } + }, +}; diff --git a/comm/suite/browser/content.js b/comm/suite/browser/content.js new file mode 100644 index 0000000000..40dd01d7c2 --- /dev/null +++ b/comm/suite/browser/content.js @@ -0,0 +1,901 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This content script should work in any browser or iframe and should not + * depend on the frame being contained in tabbrowser. */ + +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +ChromeUtils.defineModuleGetter(this, "LoginManagerContent", + "resource://gre/modules/LoginManagerContent.jsm"); +ChromeUtils.defineModuleGetter(this, "InsecurePasswordUtils", + "resource://gre/modules/InsecurePasswordUtils.jsm"); +ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); +ChromeUtils.defineModuleGetter(this, "LoginFormFactory", + "resource://gre/modules/LoginManagerContent.jsm"); +ChromeUtils.defineModuleGetter(this, "PlacesUIUtils", + "resource:///modules/PlacesUIUtils.jsm"); +ChromeUtils.defineModuleGetter(this, "setTimeout", + "resource://gre/modules/Timer.jsm"); +ChromeUtils.defineModuleGetter(this, "Feeds", + "resource:///modules/Feeds.jsm"); +ChromeUtils.defineModuleGetter(this, "BrowserUtils", + "resource://gre/modules/BrowserUtils.jsm"); + +XPCOMUtils.defineLazyGetter(this, "gPipNSSBundle", function() { + return Services.strings.createBundle("chrome://pipnss/locale/pipnss.properties"); +}); +XPCOMUtils.defineLazyGetter(this, "gNSSErrorsBundle", function() { + return Services.strings.createBundle("chrome://pipnss/locale/nsserrors.properties"); +}); + +addMessageListener("RemoteLogins:fillForm", message => { + LoginManagerContent.receiveMessage(message, content); +}); + +addEventListener("DOMFormHasPassword", event => { + LoginManagerContent.onDOMFormHasPassword(event, content); + let formLike = LoginFormFactory.createFromForm(event.target); + InsecurePasswordUtils.reportInsecurePasswords(formLike); +}); + +addEventListener("DOMInputPasswordAdded", event => { + LoginManagerContent.onDOMInputPasswordAdded(event, content); + let formLike = LoginFormFactory.createFromField(event.target); + InsecurePasswordUtils.reportInsecurePasswords(formLike); +}); + +addEventListener("pageshow", event => { + LoginManagerContent.onPageShow(event, content); +}, true); + +addEventListener("DOMAutoComplete", event => { + LoginManagerContent.onUsernameInput(event); +}); + +addEventListener("blur", event => { + LoginManagerContent.onUsernameInput(event); +}); + +addMessageListener("Bookmarks:GetPageDetails", (message) => { + let doc = content.document; + let isErrorPage = /^about:(neterror|certerror|blocked)/.test(doc.documentURI); + sendAsyncMessage("Bookmarks:GetPageDetails:Result", + { isErrorPage, + description: PlacesUIUtils.getDescriptionFromDocument(doc) }); +}); + +/* The following code, in particular AboutCertErrorListener and + * AboutNetErrorListener, is mostly copied from content browser.js and content.js. + * Certificate error handling should be unified to remove this duplicated code. + */ + +const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE; +const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE; + +const SEC_ERROR_EXPIRED_CERTIFICATE = SEC_ERROR_BASE + 11; +const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13; +const SEC_ERROR_UNTRUSTED_ISSUER = SEC_ERROR_BASE + 20; +const SEC_ERROR_UNTRUSTED_CERT = SEC_ERROR_BASE + 21; +const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30; +const SEC_ERROR_CA_CERT_INVALID = SEC_ERROR_BASE + 36; +const SEC_ERROR_OCSP_FUTURE_RESPONSE = SEC_ERROR_BASE + 131; +const SEC_ERROR_OCSP_OLD_RESPONSE = SEC_ERROR_BASE + 132; +const SEC_ERROR_REUSED_ISSUER_AND_SERIAL = SEC_ERROR_BASE + 138; +const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = SEC_ERROR_BASE + 176; +const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 5; +const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 6; +const MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT = MOZILLA_PKIX_ERROR_BASE + 14; +const MOZILLA_PKIX_ERROR_MITM_DETECTED = MOZILLA_PKIX_ERROR_BASE + 15; + + +const SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE; +const SSL_ERROR_SSL_DISABLED = SSL_ERROR_BASE + 20; +const SSL_ERROR_SSL2_DISABLED = SSL_ERROR_BASE + 14; + +var AboutNetAndCertErrorListener = { + init(chromeGlobal) { + addEventListener("AboutNetAndCertErrorLoad", this, false, true); + }, + + get isNetErrorSite() { + return content.document.documentURI.startsWith("about:neterror"); + }, + + get isCertErrorSite() { + return content.document.documentURI.startsWith("about:certerror"); + }, + + _getErrorMessageFromCode(securityInfo, doc) { + let uri = Services.io.newURI(doc.location); + let hostString = uri.host; + if (uri.port != 443 && uri.port != -1) { + hostString += ":" + uri.port; + } + + let id_str = ""; + switch (securityInfo.errorCode) { + case SSL_ERROR_SSL_DISABLED: + id_str = "PSMERR_SSL_Disabled"; + break; + case SSL_ERROR_SSL2_DISABLED: + id_str = "PSMERR_SSL2_Disabled"; + break; + case SEC_ERROR_REUSED_ISSUER_AND_SERIAL: + id_str = "PSMERR_HostReusedIssuerSerial"; + break; + } + let nss_error_id_str = securityInfo.errorCodeString; + let msg2 = ""; + if (id_str) { + msg2 = gPipNSSBundle.GetStringFromName(id_str) + "\n"; + } else if (nss_error_id_str) { + msg2 = gNSSErrorsBundle.GetStringFromName(nss_error_id_str) + "\n"; + } + + if (!msg2) { + // We couldn't get an error message. Use the error string. + // Note that this is different from before where we used PR_ErrorToString. + msg2 = nss_error_id_str; + } + let msg = gPipNSSBundle.formatStringFromName("SSLConnectionErrorPrefix2", + [hostString, msg2], 2); + + if (nss_error_id_str) { + msg += gPipNSSBundle.formatStringFromName("certErrorCodePrefix3", + [nss_error_id_str], 1); + } + let id = content.document.getElementById("errorShortDescText"); + id.textContent = msg; + id.className = "wrap"; + }, + + _setTechDetails(sslStatus, securityInfo, location) { + if (!securityInfo || !sslStatus || !location) { + return; + } + let validity = sslStatus.serverCert.validity; + + let doc = content.document; + // CSS class and error code are set from nsDocShell. + let searchParams = new URLSearchParams(doc.documentURI.split("?")[1]); + let cssClass = searchParams.get("s"); + let error = searchParams.get("e"); + let technicalInfo = doc.getElementById("technicalContentText"); + + let uri = Services.io.newURI(location); + let hostString = uri.host; + if (uri.port != 443 && uri.port != -1) { + hostString += ":" + uri.port; + } + + let msg = gPipNSSBundle.formatStringFromName("certErrorIntro", + [hostString], 1); + msg += "\n\n"; + + if (sslStatus.isUntrusted) { + switch (securityInfo.errorCode) { + case MOZILLA_PKIX_ERROR_MITM_DETECTED: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_MitM") + "\n"; + break; + case SEC_ERROR_UNKNOWN_ISSUER: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_UnknownIssuer") + "\n"; + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_UnknownIssuer2") + "\n"; + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_UnknownIssuer3") + "\n"; + break; + case SEC_ERROR_CA_CERT_INVALID: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_CaInvalid") + "\n"; + break; + case SEC_ERROR_UNTRUSTED_ISSUER: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_Issuer") + "\n"; + break; + case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_SignatureAlgorithmDisabled") + "\n"; + break; + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_ExpiredIssuer") + "\n"; + break; + case MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_SelfSigned") + "\n"; + break; + case SEC_ERROR_UNTRUSTED_CERT: + default: + msg += gPipNSSBundle.GetStringFromName("certErrorTrust_Untrusted") + "\n"; + } + } + + technicalInfo.appendChild(doc.createTextNode(msg)); + + if (sslStatus.isDomainMismatch) { + let subjectAltNamesList = sslStatus.serverCert.subjectAltNames; + let subjectAltNames = subjectAltNamesList.split(","); + let numSubjectAltNames = subjectAltNames.length; + if (numSubjectAltNames != 0) { + if (numSubjectAltNames == 1) { + // Let's check if we want to make this a link. + let okHost = subjectAltNamesList; + let href = ""; + let thisHost = doc.location.hostname; + let proto = doc.location.protocol + "//"; + // If okHost is a wildcard domain ("*.example.com") let's + // use "www" instead. "*.example.com" isn't going to + // get anyone anywhere useful. bug 432491 + okHost = okHost.replace(/^\*\./, "www."); + /* case #1: + * example.com uses an invalid security certificate. + * + * The certificate is only valid for www.example.com + * + * Make sure to include the "." ahead of thisHost so that + * a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com" + * + * We'd normally just use a RegExp here except that we lack a + * library function to escape them properly (bug 248062), and + * domain names are famous for having '.' characters in them, + * which would allow spurious and possibly hostile matches. + */ + if (okHost.endsWith("." + thisHost)) { + href = proto + okHost; + } + /* case #2: + * browser.garage.maemo.org uses an invalid security certificate. + * + * The certificate is only valid for garage.maemo.org + */ + if (thisHost.endsWith("." + okHost)) { + href = proto + okHost; + } + + // If we set a link, meaning there's something helpful for + // the user here, expand the section by default + if (href && cssClass != "expertBadCert") { + doc.getElementById("technicalContentText").style.display = "block"; + } + + let msgPrefix = + gPipNSSBundle.GetStringFromName("certErrorMismatchSinglePrefix"); + + // Set the link if we want it. + if (href) { + let referrerlink = doc.createElement("a"); + referrerlink.append(subjectAltNamesList + "\n"); + referrerlink.title = subjectAltNamesList; + referrerlink.id = "cert_domain_link"; + referrerlink.href = href; + msg = BrowserUtils.getLocalizedFragment(doc, msgPrefix, + referrerlink); + } else { + msg = BrowserUtils.getLocalizedFragment(doc, msgPrefix, + subjectAltNamesList); + } + } else { + msg = gPipNSSBundle.GetStringFromName("certErrorMismatchMultiple") + "\n"; + for (let i = 0; i < numSubjectAltNames; i++) { + msg += subjectAltNames[i]; + if (i != (numSubjectAltNames - 1)) { + msg += ", "; + } + } + } + } else { + msg = gPipNSSBundle.formatStringFromName("certErrorMismatch", + [hostString], 1); + } + technicalInfo.append(msg + "\n"); + } + + if (sslStatus.isNotValidAtThisTime) { + let nowTime = new Date().getTime() * 1000; + let dateOptions = { year: "numeric", month: "long", day: "numeric", + hour: "numeric", minute: "numeric" }; + let now = new Services.intl.DateTimeFormat(undefined, dateOptions).format(new Date()); + if (validity.notBefore) { + if (nowTime > validity.notAfter) { + msg = gPipNSSBundle.formatStringFromName("certErrorExpiredNow", + [validity.notAfterLocalTime, now], 2) + "\n"; + } else { + msg = gPipNSSBundle.formatStringFromName("certErrorNotYetValidNow", + [validity.notBeforeLocalTime, now], 2) + "\n"; + } + } else { + // If something goes wrong, we assume the cert expired. + msg = gPipNSSBundle.formatStringFromName("certErrorExpiredNow", + ["", now], 2) + "\n"; + } + technicalInfo.append(msg); + } + technicalInfo.append("\n"); + + // Add link to certificate and error message. + msg = gPipNSSBundle.formatStringFromName("certErrorCodePrefix3", + [securityInfo.errorCodeString], 1); + technicalInfo.append(msg); + }, + + handleEvent(aEvent) { + if (!this.isNetErrorSite && !this.isCertErrorSite) { + return; + } + + if (aEvent.type != "AboutNetAndCertErrorLoad") { + return; + } + + if (this.isNetErrorSite) { + let {securityInfo} = docShell.failedChannel; + // We don't have a securityInfo when this is for example a DNS error. + if (securityInfo) { + securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); + this._getErrorMessageFromCode(securityInfo, + aEvent.originalTarget.ownerGlobal); + } + return; + } + + let ownerDoc = aEvent.originalTarget.ownerGlobal; + let securityInfo = docShell.failedChannel && docShell.failedChannel.securityInfo; + securityInfo.QueryInterface(Ci.nsITransportSecurityInfo) + .QueryInterface(Ci.nsISerializable); + let sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider) + .SSLStatus; + this._setTechDetails(sslStatus, securityInfo, ownerDoc.location.href); + }, +}; +AboutNetAndCertErrorListener.init(); + +const MathMLNS = "http://www.w3.org/1998/Math/MathML"; +const XLinkNS = "http://www.w3.org/1999/xlink"; + +let PageInfoListener = { + + init: function() { + addMessageListener("PageInfo:getData", this); + }, + + receiveMessage: function(message) { + let strings = message.data.strings; + let window; + let document; + + let frameOuterWindowID = message.data.frameOuterWindowID; + + // If inside frame then get the frame's window and document. + if (frameOuterWindowID) { + window = Services.wm.getOuterWindowWithId(frameOuterWindowID); + document = window.document; + } + else { + document = content.document; + window = content.window; + } + + let imageElement = message.objects.imageElement; + + let pageInfoData = {metaViewRows: this.getMetaInfo(document), + docInfo: this.getDocumentInfo(document), + feeds: this.getFeedsInfo(document, strings), + windowInfo: this.getWindowInfo(window), + imageInfo: this.getImageInfo(imageElement)}; + + sendAsyncMessage("PageInfo:data", pageInfoData); + + // Separate step so page info dialog isn't blank while waiting for this + // to finish. + this.getMediaInfo(document, window, strings); + }, + + getImageInfo: function(imageElement) { + let imageInfo = null; + if (imageElement) { + imageInfo = { + currentSrc: imageElement.currentSrc, + width: imageElement.width, + height: imageElement.height, + imageText: imageElement.title || imageElement.alt + }; + } + return imageInfo; + }, + + getMetaInfo: function(document) { + let metaViewRows = []; + + // Get the meta tags from the page. + let metaNodes = document.getElementsByTagName("meta"); + + for (let metaNode of metaNodes) { + metaViewRows.push([metaNode.name || metaNode.httpEquiv || + metaNode.getAttribute("property"), + metaNode.content]); + } + + return metaViewRows; + }, + + getWindowInfo: function(window) { + let windowInfo = {}; + windowInfo.isTopWindow = window == window.top; + + let hostName = null; + try { + hostName = window.location.host; + } + catch (exception) { } + + windowInfo.hostName = hostName; + return windowInfo; + }, + + getDocumentInfo: function(document) { + let docInfo = {}; + docInfo.title = document.title; + docInfo.location = document.location.toString(); + docInfo.referrer = document.referrer; + docInfo.compatMode = document.compatMode; + docInfo.contentType = document.contentType; + docInfo.characterSet = document.characterSet; + docInfo.lastModified = document.lastModified; + docInfo.principal = document.nodePrincipal; + + let documentURIObject = {}; + documentURIObject.spec = document.documentURIObject.spec; + docInfo.documentURIObject = documentURIObject; + + docInfo.isContentWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(content); + + return docInfo; + }, + + getFeedsInfo: function(document, strings) { + let feeds = []; + // Get the feeds from the page. + let linkNodes = document.getElementsByTagName("link"); + let length = linkNodes.length; + for (let i = 0; i < length; i++) { + let link = linkNodes[i]; + if (!link.href) { + continue; + } + let rel = link.rel && link.rel.toLowerCase(); + let rels = {}; + + if (rel) { + for (let relVal of rel.split(/\s+/)) { + rels[relVal] = true; + } + } + + if (rels.feed || (link.type && rels.alternate && !rels.stylesheet)) { + let type = Feeds.isValidFeed(link, document.nodePrincipal, "feed" in rels); + if (type) { + type = strings[type] || strings["application/rss+xml"]; + feeds.push([link.title, type, link.href]); + } + } + } + return feeds; + }, + + // Only called once to get the media tab's media elements from the content + // page. + getMediaInfo: function(document, window, strings) + { + let frameList = this.goThroughFrames(document, window); + this.processFrames(document, frameList, strings); + }, + + goThroughFrames: function(document, window) + { + let frameList = [document]; + if (window && window.frames.length > 0) { + let num = window.frames.length; + for (let i = 0; i < num; i++) { + // Recurse through the frames. + frameList = + frameList.concat(this.goThroughFrames(window.frames[i].document, + window.frames[i])); + } + } + return frameList; + }, + + async processFrames(document, frameList, strings) + { + let nodeCount = 0; + for (let doc of frameList) { + let iterator = doc.createTreeWalker(doc, content.NodeFilter.SHOW_ELEMENT); + + // Goes through all the elements on the doc. + while (iterator.nextNode()) { + this.getMediaItems(document, strings, iterator.currentNode); + + if (++nodeCount % 500 == 0) { + // setTimeout every 500 elements so we don't keep blocking the + // content process. + await new Promise(resolve => setTimeout(resolve, 10)); + } + } + } + // Send that page info media fetching has finished. + sendAsyncMessage("PageInfo:mediaData", {isComplete: true}); + }, + + getMediaItems: function(document, strings, elem) + { + // Check for images defined in CSS (e.g. background, borders). + let computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, ""); + // A node can have multiple media items associated with it - for example, + // multiple background images. + let imageItems = []; + let formItems = []; + let linkItems = []; + + let addImage = (url, type, alt, elem, isBg) => { + let element = this.serializeElementInfo(document, url, type, alt, elem, isBg); + imageItems.push([url, type, alt, element, isBg]); + }; + + if (computedStyle) { + let addImgFunc = (label, val) => { + if (val.primitiveType == content.CSSPrimitiveValue.CSS_URI) { + addImage(val.getStringValue(), label, strings.notSet, elem, true); + } + else if (val.primitiveType == content.CSSPrimitiveValue.CSS_STRING) { + // This is for -moz-image-rect. + // TODO: Reimplement once bug 714757 is fixed. + let strVal = val.getStringValue(); + if (strVal.search(/^.*url\(\"?/) > -1) { + let url = strVal.replace(/^.*url\(\"?/,"").replace(/\"?\).*$/,""); + addImage(url, label, strings.notSet, elem, true); + } + } + else if (val.cssValueType == content.CSSValue.CSS_VALUE_LIST) { + // Recursively resolve multiple nested CSS value lists. + for (let i = 0; i < val.length; i++) { + addImgFunc(label, val.item(i)); + } + } + }; + + addImgFunc(strings.mediaBGImg, computedStyle.getPropertyCSSValue("background-image")); + addImgFunc(strings.mediaBorderImg, computedStyle.getPropertyCSSValue("border-image-source")); + addImgFunc(strings.mediaListImg, computedStyle.getPropertyCSSValue("list-style-image")); + addImgFunc(strings.mediaCursor, computedStyle.getPropertyCSSValue("cursor")); + } + + let addForm = (elem) => { + let element = this.serializeFormInfo(document, elem, strings); + formItems.push([elem.name, elem.method, elem.action, element]); + }; + + // One swi^H^H^Hif-else to rule them all. + if (elem instanceof content.HTMLAnchorElement) { + linkItems.push([this.getValueText(elem), elem.href, strings.linkAnchor, + elem.target, elem.accessKey]); + } + else if (elem instanceof content.HTMLImageElement) { + addImage(elem.src, strings.mediaImg, + (elem.hasAttribute("alt")) ? elem.alt : strings.notSet, + elem, false); + } + else if (elem instanceof content.HTMLAreaElement) { + linkItems.push([elem.alt, elem.href, strings.linkArea, elem.target, ""]); + } + else if (elem instanceof content.HTMLVideoElement) { + addImage(elem.currentSrc, strings.mediaVideo, "", elem, false); + } + else if (elem instanceof content.HTMLAudioElement) { + addImage(elem.currentSrc, strings.mediaAudio, "", elem, false); + } + else if (elem instanceof content.HTMLLinkElement) { + if (elem.rel) { + let rel = elem.rel; + if (/\bicon\b/i.test(rel)) { + addImage(elem.href, strings.mediaLink, "", elem, false); + } + else if (/(?:^|\s)stylesheet(?:\s|$)/i.test(rel)) { + linkItems.push([elem.rel, elem.href, strings.linkStylesheet, + elem.target, ""]); + } + else { + linkItems.push([elem.rel, elem.href, strings.linkRel, + elem.target, ""]); + } + } + else { + linkItems.push([elem.rev, elem.href, strings.linkRev, elem.target, ""]); + } + } + else if (elem instanceof content.HTMLInputElement || + elem instanceof content.HTMLButtonElement) { + switch (elem.type.toLowerCase()) { + case "image": + addImage(elem.src, strings.mediaInput, + (elem.hasAttribute("alt")) ? elem.alt : strings.notSet, + elem, false); + // Fall through, submits, too + case "submit": + if ("form" in elem && elem.form) { + linkItems.push([elem.value || this.getValueText(elem) || + strings.linkSubmit, elem.form.action, + strings.linkSubmission, elem.form.target, ""]); + } + else { + linkItems.push([elem.value || this.getValueText(elem) || + strings.linkSubmit, "", + strings.linkSubmission, "", ""]); + } + } + } + else if (elem instanceof content.HTMLFormElement) { + addForm(elem); + } + else if (elem instanceof content.HTMLObjectElement) { + addImage(elem.data, strings.mediaObject, this.getValueText(elem), + elem, false); + } + else if (elem instanceof content.HTMLEmbedElement) { + addImage(elem.src, strings.mediaEmbed, "", elem, false); + } + else if (elem.namespaceURI == MathMLNS && elem.hasAttribute("href")) { + let href = elem.getAttribute("href"); + try { + href = makeURLAbsolute(elem.baseURI, href, + elem.ownerDocument.characterSet); + } catch (e) {} + linkItems.push([this.getValueText(elem), href, strings.linkX, "", ""]); + } + else if (elem.hasAttributeNS(XLinkNS, "href")) { + let href = elem.getAttributeNS(XLinkNS, "href"); + try { + href = makeURLAbsolute(elem.baseURI, href, + elem.ownerDocument.characterSet); + } catch (e) {} + // SVG images without an xlink:href attribute are ignored + if (elem instanceof content.SVGImageElement) { + addImage(href, strings.mediaImg, "", elem, false); + } + else { + linkItems.push([this.getValueText(elem), href, strings.linkX, "", ""]); + } + } + else if (elem instanceof content.HTMLScriptElement) { + linkItems.push([elem.type || elem.getAttribute("language") || + strings.notSet, elem.src || strings.linkScriptInline, + strings.linkScript, "", "", ""]); + } + if (imageItems.length || formItems.length || linkItems.length) { + sendAsyncMessage("PageInfo:mediaData", + {imageItems, formItems, linkItems, isComplete: false}); + } + }, + + /** + * Set up a JSON element object with all the instanceOf and other infomation + * that makePreview in pageInfo.js uses to figure out how to display the + * preview. + */ + + serializeElementInfo: function(document, url, type, alt, item, isBG) + { + let result = {}; + + let imageText; + if (!isBG && + !(item instanceof content.SVGImageElement) && + !(document instanceof content.ImageDocument)) { + imageText = item.title || item.alt; + + if (!imageText && !(item instanceof content.HTMLImageElement)) { + imageText = this.getValueText(item); + } + } + + result.imageText = imageText; + result.longDesc = item.longDesc; + result.numFrames = 1; + + if (item instanceof content.HTMLObjectElement || + item instanceof content.HTMLEmbedElement || + item instanceof content.HTMLLinkElement) { + result.mimeType = item.type; + } + + if (!result.mimeType && !isBG && + item instanceof Ci.nsIImageLoadingContent) { + let imageRequest = + item.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST); + if (imageRequest) { + result.mimeType = imageRequest.mimeType; + let image = !(imageRequest.imageStatus & imageRequest.STATUS_ERROR) && + imageRequest.image; + if (image) { + result.numFrames = image.numFrames; + } + } + } + + // If we have a data url, get the MIME type from the url. + if (!result.mimeType && url.startsWith("data:")) { + let dataMimeType = /^data:(image\/[^;,]+)/i.exec(url); + if (dataMimeType) + result.mimeType = dataMimeType[1].toLowerCase(); + } + + result.HTMLLinkElement = item instanceof content.HTMLLinkElement; + result.HTMLInputElement = item instanceof content.HTMLInputElement; + result.HTMLImageElement = item instanceof content.HTMLImageElement; + result.HTMLObjectElement = item instanceof content.HTMLObjectElement; + result.HTMLEmbedElement = item instanceof content.HTMLEmbedElement; + result.SVGImageElement = item instanceof content.SVGImageElement; + result.HTMLVideoElement = item instanceof content.HTMLVideoElement; + result.HTMLAudioElement = item instanceof content.HTMLAudioElement; + + if (isBG) { + // Items that are showing this image as a background + // image might not necessarily have a width or height, + // so we'll dynamically generate an image and send up the + // natural dimensions. + let img = content.document.createElement("img"); + img.src = url; + result.naturalWidth = img.naturalWidth; + result.naturalHeight = img.naturalHeight; + } else { + // Otherwise, we can use the current width and height + // of the image. + result.width = item.width; + result.height = item.height; + } + + if (item instanceof content.SVGImageElement) { + result.SVGImageElementWidth = item.width.baseVal.value; + result.SVGImageElementHeight = item.height.baseVal.value; + } + + result.baseURI = item.baseURI; + + return result; + }, + + serializeFormInfo: function(document, form, strings) + { + let result = {}; + + if (form.name) + result.name = form.name; + + result.encoding = form.encoding; + result.target = form.target; + result.formfields = []; + + function findFirstControl(node, document) { + function FormControlFilter(node) { + if (node instanceof content.HTMLInputElement || + node instanceof content.HTMLSelectElement || + node instanceof content.HTMLButtonElement || + node instanceof content.HTMLTextAreaElement || + node instanceof content.HTMLObjectElement) + return content.NodeFilter.FILTER_ACCEPT; + return content.NodeFilter.FILTER_SKIP; + } + + if (node.hasAttribute("for")) { + return document.getElementById(node.getAttribute("for")); + } + + var iterator = document.createTreeWalker(node, content.NodeFilter.SHOW_ELEMENT, FormControlFilter, true); + + return iterator.nextNode(); + } + + var whatfor; + var labels = []; + for (let label of form.getElementsByTagName("label")) { + var whatfor = findFirstControl(label, document); + + if (whatfor && (whatfor.form == form)) { + labels.push({label: whatfor, labeltext: this.getValueText(label)}); + } + } + + result.formfields = []; + + var val; + for (let formfield of form.elements) { + if (formfield instanceof content.HTMLButtonElement) + val = this.getValueText(formfield); + else + val = (/^password$/i.test(formfield.type)) ? strings.formPassword : formfield.value; + + var fieldlabel = ""; + for (let labelfor of labels) { + if (formfield == labelfor.label) { + fieldlabel = labelfor.labeltext; + } + } + result.formfields.push([fieldlabel, formfield.name, formfield.type, val]); + } + + return result; + }, + + //******** Other Misc Stuff + // Modified from the Links Panel v2.3, + // http://segment7.net/mozilla/links/links.html + // parse a node to extract the contents of the node + getValueText: function(node) + { + + let valueText = ""; + + // Form input elements don't generally contain information that is useful + // to our callers, so return nothing. + if (node instanceof content.HTMLInputElement || + node instanceof content.HTMLSelectElement || + node instanceof content.HTMLTextAreaElement) { + return valueText; + } + + // Otherwise recurse for each child. + let length = node.childNodes.length; + + for (let i = 0; i < length; i++) { + let childNode = node.childNodes[i]; + let nodeType = childNode.nodeType; + + // Text nodes are where the goods are. + if (nodeType == content.Node.TEXT_NODE) { + valueText += " " + childNode.nodeValue; + } + // And elements can have more text inside them. + else if (nodeType == content.Node.ELEMENT_NODE) { + // Images are special, we want to capture the alt text as if the image + // weren't there. + if (childNode instanceof content.HTMLImageElement) { + valueText += " " + this.getAltText(childNode); + } + else { + valueText += " " + this.getValueText(childNode); + } + } + } + + return this.stripWS(valueText); + }, + + // Copied from the Links Panel v2.3, + // http://segment7.net/mozilla/links/links.html. + // Traverse the tree in search of an img or area element and grab its alt tag. + getAltText: function(node) + { + let altText = ""; + + if (node.alt) { + return node.alt; + } + let length = node.childNodes.length; + for (let i = 0; i < length; i++) { + if ((altText = this.getAltText(node.childNodes[i]) != undefined)) { // stupid js warning... + return altText; + } + } + return ""; + }, + + // Copied from the Links Panel v2.3, + // http://segment7.net/mozilla/links/links.html. + // Strip leading and trailing whitespace, and replace multiple consecutive + // whitespace characters with a single space. + stripWS: function(text) + { + let middleRE = /\s+/g; + let endRE = /(^\s+)|(\s+$)/g; + + text = text.replace(middleRE, " "); + return text.replace(endRE, ""); + } +}; +PageInfoListener.init(); diff --git a/comm/suite/browser/fullScreen.js b/comm/suite/browser/fullScreen.js new file mode 100644 index 0000000000..30a666cf57 --- /dev/null +++ b/comm/suite/browser/fullScreen.js @@ -0,0 +1,107 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var FullScreen = +{ + toggle: function() + { + var show = !window.fullScreen; + // show/hide all menubars, toolbars, and statusbars (except the full screen toolbar) + this.showXULChrome("toolbar", show); + this.showXULChrome("statusbar", show); + + var toolbox = getNavToolbox(); + if (show) + toolbox.removeAttribute("inFullscreen"); + else + toolbox.setAttribute("inFullscreen", true); + + var controls = document.getElementsByAttribute("fullscreencontrol", "true"); + for (let i = 0; i < controls.length; ++i) + controls[i].hidden = show; + + controls = document.getElementsByAttribute("domfullscreenhidden", "true"); + if (document.mozFullScreen) { + for (let i = 0; i < controls.length; ++i) + controls[i].setAttribute("moz-collapsed", "true"); + getBrowser().mStrip.setAttribute("moz-collapsed", "true"); + } else { + for (let i = 0; i < controls.length; ++i) + controls[i].removeAttribute("moz-collapsed"); + getBrowser().mStrip.removeAttribute("moz-collapsed"); + } + getBrowser().getNotificationBox().notificationsHidden = document.mozFullScreen; + }, + + showXULChrome: function(aTag, aShow) + { + var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + var els = document.getElementsByTagNameNS(XULNS, aTag); + + var i; + for (i = 0; i < els.length; ++i) { + // XXX don't interfere with previously collapsed toolbars + if (els[i].getAttribute("fullscreentoolbar") == "true" && + !document.mozFullScreen) { + if (!aShow) { + var toolbarMode = els[i].getAttribute("mode"); + if (toolbarMode != "text") { + els[i].setAttribute("saved-mode", toolbarMode); + els[i].setAttribute("saved-iconsize", + els[i].getAttribute("iconsize")); + els[i].setAttribute("mode", "icons"); + els[i].setAttribute("iconsize", "small"); + } + + // XXX See bug 202978: we disable the context menu + // to prevent customization while in fullscreen, which + // causes menu breakage. + els[i].setAttribute("saved-context", + els[i].getAttribute("context")); + els[i].removeAttribute("context"); + + // Set the inFullscreen attribute to allow specific styling + // in fullscreen mode + els[i].setAttribute("inFullscreen", true); + } + else { + this.restoreAttribute(els[i], "mode"); + this.restoreAttribute(els[i], "iconsize"); + this.restoreAttribute(els[i], "context"); // XXX see above + + els[i].removeAttribute("inFullscreen"); + els[i].removeAttribute("moz-collapsed"); + } + } else if (els[i].getAttribute("type") == "menubar") { + if (aShow) { + this.restoreAttribute(els[i], "autohide"); + } + else { + els[i].setAttribute("saved-autohide", + els[i].getAttribute("autohide")); + els[i].setAttribute("autohide", "true"); + } + } else { + // use moz-collapsed so it doesn't persist hidden/collapsed, + // so that new windows don't have missing toolbars + if (aShow) + els[i].removeAttribute("moz-collapsed"); + else + els[i].setAttribute("moz-collapsed", "true"); + } + } + }, + + restoreAttribute: function(element, attributeName) + { + var savedAttribute = "saved-" + attributeName; + if (element.hasAttribute(savedAttribute)) { + var savedValue = element.getAttribute(savedAttribute); + element.setAttribute(attributeName, savedValue); + element.removeAttribute(savedAttribute); + } + } + +}; diff --git a/comm/suite/browser/hiddenWindow.xul b/comm/suite/browser/hiddenWindow.xul new file mode 100644 index 0000000000..ac769bf12c --- /dev/null +++ b/comm/suite/browser/hiddenWindow.xul @@ -0,0 +1,55 @@ + + + + + + + + + + +%brandDTD; + +%navigatorDTD; +]> + + + + + + + + + + + + + + + + + + + diff --git a/comm/suite/browser/jar.mn b/comm/suite/browser/jar.mn new file mode 100644 index 0000000000..8034462f4c --- /dev/null +++ b/comm/suite/browser/jar.mn @@ -0,0 +1,41 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +comm.jar: +% content navigator %content/navigator/ +% content navigator-region %content/navigator-region/ + content/navigator/browser-places.js + content/navigator/content.js + content/navigator/fullScreen.js + content/navigator/hiddenWindow.xul + content/navigator/linkToolbarHandler.js + content/navigator/linkToolbarItem.js + content/navigator/linkToolbarOverlay.js + content/navigator/linkToolbarOverlay.xul + content/navigator/mailNavigatorOverlay.js +* content/navigator/mailNavigatorOverlay.xul + content/navigator/metadata.js + content/navigator/metadata.xul + content/navigator/navigator.css + content/navigator/navigator.js + content/navigator/navigator.xul + content/navigator/navigatorDD.js +* content/navigator/navigatorOverlay.xul + content/navigator/nsBrowserContentListener.js + content/navigator/nsBrowserStatusHandler.js + content/navigator/sessionHistoryUI.js + content/navigator/safeBrowsingOverlay.js + content/navigator/safeBrowsingOverlay.xul + content/navigator/tabbrowser.xml + content/navigator/urlbarBindings.xml + content/navigator/webDeveloperOverlay.js + content/navigator/webDeveloperOverlay.xul + + content/navigator/pageinfo/feeds.js (pageinfo/feeds.js) + content/navigator/pageinfo/feeds.xml (pageinfo/feeds.xml) + content/navigator/pageinfo/pageInfo.css (pageinfo/pageInfo.css) + content/navigator/pageinfo/pageInfo.js (pageinfo/pageInfo.js) + content/navigator/pageinfo/pageInfo.xul (pageinfo/pageInfo.xul) + content/navigator/pageinfo/permissions.js (pageinfo/permissions.js) + content/navigator/pageinfo/security.js (pageinfo/security.js) diff --git a/comm/suite/browser/linkToolbarHandler.js b/comm/suite/browser/linkToolbarHandler.js new file mode 100644 index 0000000000..eb3e0cfb38 --- /dev/null +++ b/comm/suite/browser/linkToolbarHandler.js @@ -0,0 +1,295 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +ChromeUtils.defineModuleGetter(this, "Feeds", + "resource:///modules/Feeds.jsm"); + +/** + * LinkToolbarHandler is a Singleton that displays LINK elements + * and nodeLists of LINK elements in the Link Toolbar. It + * associates the LINK with a corresponding LinkToolbarItem based + * on it's REL attribute and the toolbar item's ID attribute. + * LinkToolbarHandler is also a Factory and will create + * LinkToolbarItems as necessary. + */ +function LinkToolbarHandler() +{ + this.items = new Array(); + this.hasItems = false; +} + +LinkToolbarHandler.prototype.handle = +function(element) +{ + // XXX: if you're going to re-enable handling of anchor elements, + // you'll want to change this to AnchorElementDecorator + var linkElement = new LinkElementDecorator(element); + + if (linkElement.isIgnored()) return; + + for (var i = 0; i < linkElement.relValues.length; i++) { + // Skip "alternate" when we have e.g. "alternate XXX". + if (linkElement.relValues.length > 1 && + linkElement.relValues[i] == "alternate") + continue; + + var linkType = LinkToolbarHandler.getLinkType(linkElement.relValues[i], element); + if (linkType) { + if (!this.hasItems) { + this.hasItems = true; + linkToolbarUI.activate(); + } + this.getItemForLinkType(linkType).displayLink(linkElement); + } + } +} + +LinkToolbarHandler.getLinkType = +function(relAttribute, element) +{ + var isFeed = false; + switch (relAttribute.toLowerCase()) { + case "top": + case "origin": + return "top"; + + case "up": + case "parent": + return "up"; + + case "start": + case "begin": + case "first": + return "first"; + + case "next": + case "child": + return "next"; + + case "prev": + case "previous": + return "prev"; + + case "end": + case "last": + return "last"; + + case "author": + case "made": + return "author"; + + case "contents": + case "toc": + return "toc"; + + case "feed": + isFeed = true; + // fall through + case "alternate": + if (Feeds.isValidFeed(element, element.nodePrincipal, isFeed)) { + return "feed"; + } + + if (!isFeed) { + return "alternate"; + } + // fall through + case "prefetch": + return null; + + default: + return relAttribute.toLowerCase(); + } +} + +LinkToolbarHandler.prototype.getItemForLinkType = +function(linkType) { + if (!(linkType in this.items && this.items[linkType])) + this.items[linkType] = LinkToolbarHandler.createItemForLinkType(linkType); + + return this.items[linkType]; +} + +LinkToolbarHandler.createItemForLinkType = +function(linkType) +{ + if (!document.getElementById("link-" + linkType)) + return new LinkToolbarTransientMenu(linkType); + + // XXX: replace switch with polymorphism + var element = document.getElementById("link-" + linkType); + switch (element.getAttribute("type") || element.localName) { + case "toolbarbutton": + return new LinkToolbarButton(linkType); + + case "menuitem": + return new LinkToolbarItem(linkType); + + case "menu": + return new LinkToolbarMenu(linkType); + + default: + return new LinkToolbarTransientMenu(linkType); + } +} + +LinkToolbarHandler.prototype.clearAllItems = +function() +{ + // Hide the 'miscellaneous' separator + document.getElementById("misc-separator").setAttribute("collapsed", "true"); + + // Disable the individual items + for (var linkType in this.items) + this.items[linkType].clear(); + + // Store the fact that the toolbar is empty + this.hasItems = false; +} + +var linkToolbarHandler = new LinkToolbarHandler(); + +function LinkElementDecorator(element) { + /* + * XXX: this is an incomplete decorator, because it doesn't implement + * the full Element interface. If you need to use a method + * or member in the Element interface, just add it here and + * have it delegate to this.element + * + * XXX: would rather add some methods to Element.prototype instead of + * using a decorator, but Element.prototype is no longer exposed + * since the XPCDOM landing, see bug 83433 + */ + + if (!element) return; // skip the rest on foo.prototype = new ThisClass calls + + this.element = element; + + this.rel = LinkElementDecorator.convertRevMade(element.rel, element.rev); + if (this.rel) + this.relValues = this.rel.split(" "); + this.rev = element.rev; + this.title = element.title; + this.href = element.href; + this.hreflang = element.hreflang; + this.media = element.media; + this.longTitle = null; +} + +LinkElementDecorator.prototype.isIgnored = +function() +{ + if (!this.rel) return true; + for (var i = 0; i < this.relValues.length; i++) + if (/^stylesheet$|^icon$|^fontdef$|^p3pv|^schema./i.test(this.relValues[i])) + return true; + return false; +} + +LinkElementDecorator.convertRevMade = +function(rel, rev) +{ + if (!rel && rev && /\bmade\b/i.test(rev)) + return rev; + else + return rel; +} + +LinkElementDecorator.prototype.getTooltip = +function() +{ + return this.getLongTitle() || this.href; +} + +LinkElementDecorator.prototype.getLabel = +function() +{ + return this.getLongTitle() || this.rel; +} + +LinkElementDecorator.prototype.getLongTitle = +function() +{ + if (this.longTitle == null) + this.longTitle = this.makeLongTitle(); + + return this.longTitle; +} + +LinkElementDecorator.prototype.makeLongTitle = +function() +{ + let prefix = ""; + + // XXX: lookup more meaningful and localized version of media, + // i.e. media="print" becomes "Printable" or some such + // XXX: use localized version of ":" separator + if (this.media && !/\ball\b|\bscreen\b/i.test(this.media)) + prefix += this.media + ": "; + if (this.hreflang) { + try { + let languageBundle = document.getElementById("languageBundle"); + let regionBundle = document.getElementById("regionBundle"); + + // In case hreflang contains region code. + let ISOcode = this.hreflang.split("-"); + + prefix += languageBundle.getString(ISOcode[0].toLowerCase()); + + // Test if region code exists. + if (ISOcode[1]) + prefix += " (" + regionBundle.getString(ISOcode[1].toLowerCase()) + ")"; + } + catch (e) { + // Return if language or region is not recognized. + prefix += gNavigatorBundle.getFormattedString("unknownLanguage", + [this.hreflang]); + } + + prefix += ": "; + } + + return this.title ? prefix + this.title : prefix; +} + +function AnchorElementDecorator(element) { + this.constructor(element); +} +AnchorElementDecorator.prototype = new LinkElementDecorator; + +AnchorElementDecorator.prototype.getLongTitle = +function() +{ + return this.title ? this.__proto__.getLongTitle.apply(this) + : getText(this.element); +} + +AnchorElementDecorator.prototype.getText = +function(element) +{ + return condenseWhitespace(getTextRecursive(element)); +} + +AnchorElementDecorator.prototype.getTextRecursive = +function(node) +{ + var text = ""; + node.normalize(); + if (node.hasChildNodes()) { + for (var i = 0; i < node.childNodes.length; i++) { + if (node.childNodes.item(i).nodeType == Node.TEXT_NODE) + text += node.childNodes.item(i).nodeValue; + else if (node.childNodes.item(i).nodeType == Node.ELEMENT_NODE) + text += getTextRecursive(node.childNodes.item(i)); + } + } + + return text; +} + +AnchorElementDecorator.prototype.condenseWhitespace = +function(text) +{ + return text.replace(/\W*$/, "").replace(/^\W*/, "").replace(/\W+/g, " "); +} diff --git a/comm/suite/browser/linkToolbarItem.js b/comm/suite/browser/linkToolbarItem.js new file mode 100644 index 0000000000..1ae956a64d --- /dev/null +++ b/comm/suite/browser/linkToolbarItem.js @@ -0,0 +1,215 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * LinkToolbarItem and its subclasses represent the buttons, menuitems, + * and menus that handle the various link types. + */ +function LinkToolbarItem (linkType) { + this.linkType = linkType; + this.xulElementId = "link-" + linkType; + this.xulPopupId = this.xulElementId + "-popup"; + this.parentMenuButton = null; + + this.getXULElement = function() { + return document.getElementById(this.xulElementId); + } + + this.clear = function() { + this.disableParentMenuButton(); + this.getXULElement().setAttribute("disabled", "true"); + this.getXULElement().removeAttribute("href"); + } + + this.displayLink = function(linkElement) { + if (this.getXULElement().hasAttribute("href")) return false; + + this.setItem(linkElement); + this.enableParentMenuButton(); + return true; + } + + this.setItem = function(linkElement) { + this.getXULElement().setAttribute("href", linkElement.href); + this.getXULElement().removeAttribute("disabled"); + } + + this.enableParentMenuButton = function() { + if(this.getParentMenuButton()) + this.getParentMenuButton().removeAttribute("disabled"); + } + + this.disableParentMenuButton = function() { + if (!this.parentMenuButton) return; + + this.parentMenuButton.setAttribute("disabled", "true"); + this.parentMenuButton = null; + } + + this.getParentMenuButton = function() { + if (!this.parentMenuButton) + this.parentMenuButton = getParentMenuButtonRecursive( + this.getXULElement()); + + return this.parentMenuButton; + } + + function getParentMenuButtonRecursive(xulElement) { + if (!xulElement) return null; + + if (xulElement.tagName == "toolbarbutton") + return xulElement; + + return getParentMenuButtonRecursive(xulElement.parentNode) + } +} + + +function LinkToolbarButton (linkType) { + this.constructor(linkType); + + this.clear = function() { + this.__proto__.clear.apply(this); + + this.getXULElement().removeAttribute("tooltiptext"); + } + + this.setItem = function(linkElement) { + this.__proto__.setItem.apply(this, [linkElement]); + + this.getXULElement().setAttribute("tooltiptext", linkElement.getTooltip()); + } + + this.enableParentMenuButton = function() { /* do nothing */ } + this.disableParentMenuButton = function() { /* do nothing */ } +} +LinkToolbarButton.prototype = new LinkToolbarItem; + + +function LinkToolbarMenu (linkType) { + this.constructor(linkType); + + this.clear = function() { + this.disableParentMenuButton(); + this.getXULElement().setAttribute("disabled", "true"); + clearPopup(this.getPopup()); + } + + function clearPopup(popup) { + while (popup.hasChildNodes()) + popup.lastChild.remove(); + } + + this.getPopup = function() { + return document.getElementById(this.xulPopupId); + } + + this.displayLink = function(linkElement) { + this.addMenuItem(linkElement); + this.getXULElement().removeAttribute("disabled"); + this.enableParentMenuButton(); + return true; + } + + function match(first, second) { + if (!first && !second) return true; + if (!first || !second) return false; + + return first == second; + } + + this.addMenuItem = function(linkElement) { + this.getPopup().appendChild(this.createMenuItem(linkElement)); + } + + this.createMenuItem = function(linkElement) { + // XXX: clone a prototypical XUL element instead of hardcoding these + // attributes + var menuitem = document.createElement("menuitem"); + menuitem.setAttribute("label", linkElement.getLabel()); + menuitem.setAttribute("href", linkElement.href); + menuitem.setAttribute("class", "menuitem-iconic bookmark-item"); + + return menuitem; + } +} +LinkToolbarMenu.prototype = new LinkToolbarItem; + + +function LinkToolbarTransientMenu (linkType) { + this.constructor(linkType); + + this.getXULElement = function() { + if (this.__proto__.getXULElement.apply(this)) + return this.__proto__.getXULElement.apply(this); + else + return this.createXULElement(); + } + + this.createXULElement = function() { + // XXX: clone a prototypical XUL element instead of hardcoding these + // attributes + var menu = document.createElement("menu"); + menu.setAttribute("id", this.xulElementId); + menu.setAttribute("label", this.linkType); + menu.setAttribute("disabled", "true"); + menu.setAttribute("class", "menu-iconic bookmark-item"); + menu.setAttribute("container", "true"); + + document.getElementById("more-menu-popup").appendChild(menu); + + return menu; + } + + this.getPopup = function() { + if (!this.__proto__.getPopup.apply(this)) + this.getXULElement().appendChild(this.createPopup()); + + return this.__proto__.getPopup.apply(this) + } + + this.createPopup = function() { + var popup = document.createElement("menupopup"); + popup.setAttribute("id", this.xulPopupId); + + return popup; + } + + this.clear = function() { + this.__proto__.clear.apply(this); + + // XXX: we really want to use this instead of removeXULElement + //this.hideXULElement(); + this.removeXULElement(); + } + + this.hideXULElement = function() { + /* + * XXX: using "hidden" or "collapsed" leads to a crash when you + * open the More menu under certain circumstances. Maybe + * related to bug 83906. As of 0.9.2 I it doesn't seem + * to crash anymore. + */ + this.getXULElement().setAttribute("collapsed", "true"); + } + + this.removeXULElement = function() { + // XXX: stop using this method once it's safe to use hideXULElement + if (this.__proto__.getXULElement.apply(this)) + this.__proto__.getXULElement.apply(this).remove(); + } + + this.displayLink = function(linkElement) { + if(!this.__proto__.displayLink.apply(this, [linkElement])) return false; + + this.getXULElement().removeAttribute("collapsed"); + + // Show the 'miscellaneous' separator + document.getElementById("misc-separator").removeAttribute("collapsed"); + return true; + } +} + +LinkToolbarTransientMenu.prototype = new LinkToolbarMenu; + diff --git a/comm/suite/browser/linkToolbarOverlay.js b/comm/suite/browser/linkToolbarOverlay.js new file mode 100644 index 0000000000..94927ad310 --- /dev/null +++ b/comm/suite/browser/linkToolbarOverlay.js @@ -0,0 +1,213 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var LinkToolbarUI = function() +{ +} + +LinkToolbarUI.prototype.linkAdded = +function(event) +{ + var element = event.originalTarget; + + if (element.ownerDocument != getBrowser().contentDocument || + !linkToolbarUI.isLinkToolbarEnabled() || + !(ChromeUtils.getClassName(element) === "HTMLLinkElement") || + !element.href || (!element.rel && !element.rev)) + return; + + linkToolbarHandler.handle(element); +} + +LinkToolbarUI.prototype.isLinkToolbarEnabled = +function() +{ + if (document.getElementById("linktoolbar").getAttribute("hidden") == "true") + return false; + else + return true; +} + +LinkToolbarUI.prototype.clear = +function(event) +{ + if (event.originalTarget != getBrowser().contentDocument || + !linkToolbarUI.isLinkToolbarEnabled() || + !linkToolbarHandler.hasItems) + return; + + linkToolbarHandler.clearAllItems(); +} + +LinkToolbarUI.prototype.tabSelected = +function(event) +{ + if (event.originalTarget.localName != "tabs" || + !linkToolbarUI.isLinkToolbarEnabled()) + return; + + linkToolbarHandler.clearAllItems(); + linkToolbarUI.deactivate(); + linkToolbarUI.fullSlowRefresh(); +} + +LinkToolbarUI.prototype.fullSlowRefresh = +function() +{ + var currentNode = getBrowser().contentDocument.documentElement; + if (!ChromeUtils.getClassName(currentNode) === "HTMLHtmlElement") + return; + currentNode = currentNode.firstChild; + + while(currentNode) + { + if (ChromeUtils.getClassName(currentNode) === "HTMLHeadElement") { + currentNode = currentNode.firstChild; + + while(currentNode) + { + if (ChromeUtils.getClassName(currentNode) === "HTMLLinkElement") + linkToolbarUI.linkAdded({originalTarget: currentNode}); + currentNode = currentNode.nextSibling; + } + } + else if (currentNode.nodeType == currentNode.ELEMENT_NODE) { + // head is supposed to be the first element inside html. + // Got something else instead. returning + return; + } + else + { + // Got a comment node or something like that. Moving on. + currentNode = currentNode.nextSibling; + } + } +} + +LinkToolbarUI.prototype.toolbarActive = false; + +LinkToolbarUI.prototype.activate = +function() +{ + if (!linkToolbarUI.toolbarActive) { + linkToolbarUI.toolbarActive = true; + document.getElementById("linktoolbar").setAttribute("hasitems", "true"); + var contentArea = document.getElementById("appcontent"); + contentArea.addEventListener("pagehide", linkToolbarUI.clear, true); + contentArea.addEventListener("pageshow", linkToolbarUI.deactivate, true); + contentArea.addEventListener("DOMHeadLoaded", linkToolbarUI.deactivate, + true); + } +} + +LinkToolbarUI.prototype.deactivate = +function() +{ + // This function can never be called unless the toolbar is active, because + // it's a handler that's only activated in that situation, so there's no need + // to check toolbarActive. On the other hand, by the time this method is + // called the toolbar might have been populated again already, in which case + // we don't want to deactivate. + if (!linkToolbarHandler.hasItems) { + linkToolbarUI.toolbarActive = false; + document.getElementById("linktoolbar").setAttribute("hasitems", "false"); + var contentArea = document.getElementById("appcontent"); + contentArea.removeEventListener("pagehide", linkToolbarUI.clear, true); + contentArea.removeEventListener("pageshow", linkToolbarUI.deactivate, true); + contentArea.removeEventListener("DOMHeadLoaded", linkToolbarUI.deactivate, + true); + } +} + +/* called whenever something on the toolbar gets an oncommand event */ +LinkToolbarUI.prototype.commanded = +function(event) +{ + // Return if this is one of the menubuttons. + if (event.target.getAttribute("type") == "menu") return; + + if (!event.target.getAttribute("href")) return; + + var destURL = event.target.getAttribute("href"); + + // We have to do a security check here, because we are loading URIs given + // to us by a web page from chrome, which is privileged. + try { + urlSecurityCheck(destURL, content.document.nodePrincipal, + Ci.nsIScriptSecurityManager.STANDARD); + loadURI(destURL, content.document.documentURIObject); + } catch (e) { + dump("Error: it is not permitted to load this URI from a element: " + e); + } +} + +// functions for twiddling XUL elements in the toolbar + +LinkToolbarUI.prototype.toggleLinkToolbar = +function(checkedItem) +{ + this.goToggleTristateToolbar("linktoolbar", checkedItem); + this.initHandlers(); + if (this.isLinkToolbarEnabled()) + this.fullSlowRefresh(); + else + linkToolbarHandler.clearAllItems(); +} + +LinkToolbarUI.prototype.initLinkbarVisibilityMenu = +function() +{ + var state = document.getElementById("linktoolbar").getAttribute("hidden"); + if (!state) + state = "maybe"; + var checkedItem = document.getElementById("cmd_viewlinktoolbar_" + state); + checkedItem.setAttribute("checked", true); + checkedItem.checked = true; +} + +LinkToolbarUI.prototype.goToggleTristateToolbar = +function(id, checkedItem) +{ + var toolbar = document.getElementById(id); + if (toolbar) + { + toolbar.setAttribute("hidden", checkedItem.value); + document.persist(id, "hidden"); + } +} + +LinkToolbarUI.prototype.addHandlerActive = false; + +LinkToolbarUI.prototype.initialized = false; + +LinkToolbarUI.prototype.initHandlers = +function() +{ + var contentArea = document.getElementById("appcontent"); + if (linkToolbarUI.isLinkToolbarEnabled()) + { + if (!linkToolbarUI.addHandlerActive) { + contentArea.addEventListener("select", linkToolbarUI.tabSelected); + contentArea.addEventListener("DOMLinkAdded", linkToolbarUI.linkAdded, + true); + linkToolbarUI.addHandlerActive = true; + } + } else + { + if (linkToolbarUI.addHandlerActive) { + contentArea.removeEventListener("select", linkToolbarUI.tabSelected); + contentArea.removeEventListener("DOMLinkAdded", linkToolbarUI.linkAdded, + true); + linkToolbarUI.addHandlerActive = false; + } + } + if (!linkToolbarUI.initialized) + { + linkToolbarUI.initialized = true; + document.removeEventListener("pageshow", linkToolbarUI.initHandlers, true); + } +} + +var linkToolbarUI = new LinkToolbarUI; + diff --git a/comm/suite/browser/linkToolbarOverlay.xul b/comm/suite/browser/linkToolbarOverlay.xul new file mode 100644 index 0000000000..8d9b4c1503 --- /dev/null +++ b/comm/suite/browser/linkToolbarOverlay.xul @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/comm/suite/browser/mailNavigatorOverlay.js b/comm/suite/browser/mailNavigatorOverlay.js new file mode 100644 index 0000000000..495d124eba --- /dev/null +++ b/comm/suite/browser/mailNavigatorOverlay.js @@ -0,0 +1,176 @@ +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var gUseExternalMailto; + +// attachment: 0 - link +// 1 - page +// 2 - image +function openComposeWindow(url, title, attachment, charset) +{ + if (gUseExternalMailto) + { + openExternalMailer(url, title); + } + else + { + var params = Cc["@mozilla.org/messengercompose/composeparams;1"] + .createInstance(Ci.nsIMsgComposeParams); + + params.composeFields = Cc["@mozilla.org/messengercompose/composefields;1"] + .createInstance(Ci.nsIMsgCompFields); + if (attachment == 0 || attachment == 1) + { + params.composeFields.body = url; + params.composeFields.subject = title; + params.bodyIsLink = true; + } + + if (attachment == 1 || attachment == 2) + { + var attachmentData = Cc["@mozilla.org/messengercompose/attachment;1"] + .createInstance(Ci.nsIMsgAttachment); + attachmentData.url = url; + attachmentData.urlCharset = charset; + params.composeFields.addAttachment(attachmentData); + } + + var composeService = Cc["@mozilla.org/messengercompose;1"] + .getService(Ci.nsIMsgComposeService); + + // it is possible you won't have a default identity + // like if you've never launched mail before on a new profile. + // see bug #196073 + try + { + params.identity = composeService.defaultIdentity; + } + catch (ex) + { + params.identity = null; + } + + composeService.OpenComposeWindowWithParams(null, params); + } +} + +function openExternalMailer(url, title) { + var extProtocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"] + .getService(Ci.nsIExternalProtocolService); + var mailto = url ? "mailto:?body=" + encodeURIComponent(url) + + "&subject=" + + encodeURIComponent(title) : "mailto:"; + var uri = Services.io.newURI(mailto); + + extProtocolSvc.loadURI(uri); +} + +function openNewCardDialog() +{ + window.openDialog("chrome://messenger/content/addressbook/abNewCardDialog.xul", + "", "chrome,modal,resizable=no,centerscreen"); +} + +function goOpenNewMessage() +{ + if (gUseExternalMailto) + { + openExternalMailer(); + } + else if ("MsgNewMessage" in window) + { + MsgNewMessage(null); + } + else + { + var msgComposeService = Cc["@mozilla.org/messengercompose;1"] + .getService(Ci.nsIMsgComposeService); + msgComposeService.OpenComposeWindow(null, null, null, + Ci.nsIMsgCompType.New, + Ci.nsIMsgCompFormat.Default, + null, null, null); + } +} + +function sendLink(aURL) +{ + var title = ""; + if (!aURL) + { + aURL = window.content.document.URL; + title = window.content.document.title; + } + try + { + openComposeWindow(aURL, title, 0, null); + } + catch(ex) + { + dump("Cannot Send Link: " + ex + "\n"); + } +} + +function sendMedia(mediaURL) +{ + try + { + var charset = getCharsetforSave(null); + openComposeWindow(mediaURL, null, 2, charset); + } + catch(ex) + { + dump("Cannot Send Media: " + ex + "\n"); + } +} + +function sendPage(aDocument) +{ + if (!aDocument) + aDocument = window.content.document; + + try + { + var charset = getCharsetforSave(aDocument); + openComposeWindow(aDocument.URL, aDocument.title, 1, charset); + } + catch(ex) + { + dump("Cannot Send Page: " + ex + "\n"); + } +} + +function initMailContextMenuItems(aEvent) +{ + var shouldShowSendPage = !(gContextMenu.onTextInput || gContextMenu.isContentSelected || + gContextMenu.onVideo || gContextMenu.onAudio) && + !gContextMenu.onLink && + !gUseExternalMailto; + gContextMenu.showItem("context-sendpage", shouldShowSendPage); + + gContextMenu.showItem("context-sep-apps", gContextMenu.shouldShowSeparator("context-sep-apps")); +} + +function initMailContextMenuPopupListener(aEvent) +{ + var popup = document.getElementById("contentAreaContextMenu"); + if (popup) + popup.addEventListener("popupshowing", initMailContextMenuItems); +} + +function hideMenuitems() { + document.getElementById("menu_newCard").hidden = gUseExternalMailto; + var menu_sendPage = document.getElementById("menu_sendPage"); + if (menu_sendPage) + menu_sendPage.hidden = gUseExternalMailto; +} + +function initOverlay(aEvent) { + gUseExternalMailto = Services.io.getProtocolHandler("mailto") instanceof + Ci.nsIExternalProtocolHandler; + initMailContextMenuPopupListener(aEvent); + hideMenuitems(); +} + +addEventListener("load", initOverlay, false); diff --git a/comm/suite/browser/mailNavigatorOverlay.xul b/comm/suite/browser/mailNavigatorOverlay.xul new file mode 100644 index 0000000000..5221ce68f8 --- /dev/null +++ b/comm/suite/browser/mailNavigatorOverlay.xul @@ -0,0 +1,96 @@ + + + + + + + + '; + +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + + gBrowser.selectedBrowser.addEventListener("load", function loadListener() { + gBrowser.selectedBrowser.removeEventListener("load", loadListener, true); + executeSoon(function () { + + // The test page loaded, so open an empty tab, select it, then restore + // the test tab. This causes the test page's focused element to be removed + // from its document. + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.removeCurrentTab(); + + // Make sure focus is given to the window because the element is now gone. + is(document.commandDispatcher.focusedWindow, window.content, + "content window is focused"); + + gBrowser.removeCurrentTab(); + finish(); + }); + }, true); + + content.location = testPage; +} diff --git a/comm/suite/browser/test/browser/browser_bug435325.js b/comm/suite/browser/test/browser/browser_bug435325.js new file mode 100644 index 0000000000..dca45a6001 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_bug435325.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Ensure that clicking the button in the Offline mode neterror page makes the browser go online. See bug 435325. */ + +var proxyPrefValue; + +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + window.addEventListener("DOMContentLoaded", checkPage); + + + // Tests always connect to localhost, and per bug 87717, localhost is now + // reachable in offline mode. To avoid this, disable any proxy. + proxyPrefValue = Services.prefs.getIntPref("network.proxy.type"); + Services.prefs.setIntPref("network.proxy.type", 0); + + // Go offline and disable the proxy and cache, then try to load the test URL. + Services.io.offline = true; + Services.prefs.setBoolPref("browser.cache.disk.enable", false); + Services.prefs.setBoolPref("browser.cache.memory.enable", false); + content.location = "http://example.com/"; +} + +function checkPage() { + if(content.location == "about:blank") { + info("got about:blank, which is expected once, so return"); + return; + } + + window.removeEventListener("DOMContentLoaded", checkPage); + + ok(Services.io.offline, "Setting Services.io.offline to true."); + is(gBrowser.contentDocument.documentURI.substring(0,27), + "about:neterror?e=netOffline", "Loading the Offline mode neterror page."); + + // Now press the "Try Again" button + ok(gBrowser.contentDocument.getElementById("errorTryAgain"), + "The error page has got a #errorTryAgain element"); + gBrowser.contentDocument.getElementById("errorTryAgain").click(); + + ok(!Services.io.offline, "After clicking the Try Again button, we're back " + + "online."); + + finish(); +} + +registerCleanupFunction(function() { + Services.prefs.setIntPref("network.proxy.type", proxyPrefValue); + Services.prefs.setBoolPref("browser.cache.disk.enable", true); + Services.prefs.setBoolPref("browser.cache.memory.enable", true); + Services.io.offline = false; + gBrowser.removeCurrentTab(); +}); diff --git a/comm/suite/browser/test/browser/browser_bug462289.js b/comm/suite/browser/test/browser/browser_bug462289.js new file mode 100644 index 0000000000..c61b9a0403 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_bug462289.js @@ -0,0 +1,87 @@ +// Wanted delay (in ms) to let UI fully update. +// 375: hopefully enough (on slow test environments). +var gDelay = 375; + +var tab1, tab2; + +function focus_in_navbar() +{ + var parent = document.activeElement.parentNode; + while (parent && parent.id != "nav-bar") + parent = parent.parentNode; + + return parent != null; +} + +function test() +{ + waitForExplicitFinish(); + + // Ftr, SeaMonkey doesn't support animation (yet). + tab1 = gBrowser.addTab("about:blank"); + tab2 = gBrowser.addTab("about:blank"); + + EventUtils.synthesizeMouseAtCenter(tab1, {}); + setTimeout(step2, gDelay); +} + +function step2() +{ + is(gBrowser.selectedTab, tab1, "1st click on tab1 selects tab"); + isnot(document.activeElement, tab1, "1st click on tab1 does not activate tab"); + + EventUtils.synthesizeMouseAtCenter(tab1, {}); + setTimeout(step3, gDelay); +} + +function step3() +{ + is(gBrowser.selectedTab, tab1, "2nd click on selected tab1 keeps tab selected"); + // SeaMonkey differs from Firefox. + is(document.activeElement, tab1, "2nd click on selected tab1 activates tab"); + + // Ftr, SeaMonkey doesn't support tabsontop (yet). + ok(true, "focusing URLBar then sending Tab(s) until out of nav-bar."); + document.getElementById("urlbar").focus(); + while (focus_in_navbar()) + EventUtils.synthesizeKey("VK_TAB", { }); + is(gBrowser.selectedTab, tab1, "tab key to selected tab1 keeps tab selected"); + is(document.activeElement, tab1, "tab key to selected tab1 activates tab"); + + EventUtils.synthesizeMouseAtCenter(tab1, {}); + setTimeout(step4, gDelay); +} + +function step4() +{ + is(gBrowser.selectedTab, tab1, "3rd click on activated tab1 keeps tab selected"); + is(document.activeElement, tab1, "3rd click on activated tab1 keeps tab activated"); + + EventUtils.synthesizeMouseAtCenter(tab2, {}); + setTimeout(step5, gDelay); +} + +function step5() +{ + // The tabbox selects a tab within a setTimeout in a bubbling mousedown event + // listener, and focuses the current tab if another tab previously had focus. + is(gBrowser.selectedTab, tab2, "click on tab2 while tab1 is activated selects tab"); + is(document.activeElement, tab2, "click on tab2 while tab1 is activated activates tab"); + + ok(true, "focusing content then sending middle-button mousedown to tab2."); + content.focus(); + EventUtils.synthesizeMouseAtCenter(tab2, {button: 1, type: "mousedown"}); + setTimeout(step6, gDelay); +} + +function step6() +{ + is(gBrowser.selectedTab, tab2, "middle-button mousedown on selected tab2 keeps tab selected"); + // SeaMonkey differs from Firefox. + is(document.activeElement, tab2, "middle-button mousedown on selected tab2 activates tab"); + + gBrowser.removeTab(tab2); + gBrowser.removeTab(tab1); + + finish(); +} diff --git a/comm/suite/browser/test/browser/browser_bug519216.js b/comm/suite/browser/test/browser/browser_bug519216.js new file mode 100644 index 0000000000..a924f7a091 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_bug519216.js @@ -0,0 +1,50 @@ +function test() { + waitForExplicitFinish(); + gBrowser.stop(); + gBrowser.addProgressListener(progressListener1); + gBrowser.addProgressListener(progressListener2); + gBrowser.addProgressListener(progressListener3); + gBrowser.loadURI("data:text/plain,bug519216"); +} + +var calledListener1 = false; +var progressListener1 = { + onLocationChange: function onLocationChange() { + calledListener1 = true; + gBrowser.removeProgressListener(this); + } +}; + +var calledListener2 = false; +var progressListener2 = { + onLocationChange: function onLocationChange() { + ok(calledListener1, "called progressListener1 before progressListener2"); + calledListener2 = true; + gBrowser.removeProgressListener(this); + } +}; + +var progressListener3 = { + onLocationChange: function onLocationChange() { + ok(calledListener2, "called progressListener2 before progressListener3"); + gBrowser.removeProgressListener(this); + gBrowser.addProgressListener(progressListener4); + executeSoon(function () { + expectListener4 = true; + gBrowser.reload(); + }); + } +}; + +var expectListener4 = false; +var progressListener4 = { + onLocationChange: function onLocationChange() { + ok(expectListener4, "didn't call progressListener4 for the first location change"); + gBrowser.removeProgressListener(this); + executeSoon(function () { + gBrowser.addTab(); + gBrowser.removeCurrentTab(); + finish(); + }); + } +}; diff --git a/comm/suite/browser/test/browser/browser_bug561636.js b/comm/suite/browser/test/browser/browser_bug561636.js new file mode 100644 index 0000000000..8f41629253 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_bug561636.js @@ -0,0 +1,459 @@ +var gInvalidFormPopup = document.getElementById('invalid-form-popup'); +ok(gInvalidFormPopup, + "The browser should have a popup to show when a form is invalid"); + +function checkPopupShow() +{ + ok(gInvalidFormPopup.state == 'showing' || gInvalidFormPopup.state == 'open', + "The invalid form popup should be shown"); +} + +function checkPopupHide() +{ + ok(gInvalidFormPopup.state != 'showing' && gInvalidFormPopup.state != 'open', + "The invalid form popup should not be shown"); +} + +function checkPopupMessage(doc) +{ + is(gInvalidFormPopup.firstChild.textContent, + doc.getElementById('i').validationMessage, + "The panel should show the message from validationMessage"); +} + +var gObserver = { + QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]), + + notifyInvalidSubmit : function (aFormElement, aInvalidElements) + { + } +}; + +function test() +{ + waitForExplicitFinish(); + + test1(); +} + +/** + * In this test, we check that no popup appears if the form is valid. + */ +function test1() { + let uri = "data:text/html,
"; + let tab = gBrowser.addTab(); + + tab.linkedBrowser.addEventListener("load", function test1TabLBLoad(aEvent) { + tab.linkedBrowser.removeEventListener("load", test1TabLBLoad, true); + let doc = gBrowser.contentDocument; + + doc.getElementById('s').click(); + + executeSoon(function() { + checkPopupHide(); + + // Clean-up + gBrowser.removeTab(gBrowser.selectedTab); + + // Next test + executeSoon(test2); + }); + }, true); + + gBrowser.selectedTab = tab; + gBrowser.selectedTab.linkedBrowser.loadURI(uri); +} + +/** + * In this test, we check that, when an invalid form is submitted, + * the invalid element is focused and a popup appears. + */ +function test2() +{ + let uri = "data:text/html,
"; + let tab = gBrowser.addTab(); + + gInvalidFormPopup.addEventListener("popupshown", function test2gIpopupShown() { + gInvalidFormPopup.removeEventListener("popupshown", test2gIpopupShown); + + let doc = gBrowser.contentDocument; + is(doc.activeElement, doc.getElementById('i'), + "First invalid element should be focused"); + + checkPopupShow(); + checkPopupMessage(doc); + + // Clean-up and next test. + gBrowser.removeTab(gBrowser.selectedTab); + executeSoon(test3); + }); + + tab.linkedBrowser.addEventListener("load", function test2TabLBLoad(aEvent) { + tab.linkedBrowser.removeEventListener("load", test2TabLBLoad, true); + + gBrowser.contentDocument.getElementById('s').click(); + }, true); + + gBrowser.selectedTab = tab; + gBrowser.selectedTab.linkedBrowser.loadURI(uri); +} + +/** + * In this test, we check that, when an invalid form is submitted, + * the first invalid element is focused and a popup appears. + */ +function test3() +{ + let uri = "data:text/html,
"; + let tab = gBrowser.addTab(); + + gInvalidFormPopup.addEventListener("popupshown", function test3gIPopupShown() { + gInvalidFormPopup.removeEventListener("popupshown", test3gIPopupShown); + + let doc = gBrowser.contentDocument; + is(doc.activeElement, doc.getElementById('i'), + "First invalid element should be focused"); + + checkPopupShow(); + checkPopupMessage(doc); + + // Clean-up and next test. + gBrowser.removeTab(gBrowser.selectedTab); + executeSoon(test4a); + }); + + tab.linkedBrowser.addEventListener("load", function test3TabLBLoad(aEvent) { + tab.linkedBrowser.removeEventListener("load", test3TabLBLoad, true); + + gBrowser.contentDocument.getElementById('s').click(); + }, true); + + gBrowser.selectedTab = tab; + gBrowser.selectedTab.linkedBrowser.loadURI(uri); +} + +/** + * In this test, we check that, we hide the popup by interacting with the + * invalid element if the element becomes valid. + */ +function test4a() +{ + let uri = "data:text/html,
"; + let tab = gBrowser.addTab(); + + gInvalidFormPopup.addEventListener("popupshown", function test4agIPopupShown() { + gInvalidFormPopup.removeEventListener("popupshown", test4agIPopupShown); + + let doc = gBrowser.contentDocument; + is(doc.activeElement, doc.getElementById('i'), + "First invalid element should be focused"); + + checkPopupShow(); + checkPopupMessage(doc); + + EventUtils.synthesizeKey("a", {}); + + executeSoon(function () { + checkPopupHide(); + + // Clean-up and next test. + gBrowser.removeTab(gBrowser.selectedTab); + executeSoon(test4b); + }); + }); + + tab.linkedBrowser.addEventListener("load", function test4aTabLBLoad(aEvent) { + tab.linkedBrowser.removeEventListener("load", test4aTabLBLoad, true); + + gBrowser.contentDocument.getElementById('s').click(); + }, true); + + gBrowser.selectedTab = tab; + gBrowser.selectedTab.linkedBrowser.loadURI(uri); +} + +/** + * In this test, we check that, we don't hide the popup by interacting with the + * invalid element if the element is still invalid. + */ +function test4b() +{ + let uri = "data:text/html,
"; + let tab = gBrowser.addTab(); + + gInvalidFormPopup.addEventListener("popupshown", function test4bgIpopupShown() { + gInvalidFormPopup.removeEventListener("popupshown", test4bgIpopupShown); + + let doc = gBrowser.contentDocument; + is(doc.activeElement, doc.getElementById('i'), + "First invalid element should be focused"); + + checkPopupShow(); + checkPopupMessage(doc); + + EventUtils.synthesizeKey("a", {}); + + executeSoon(function () { + checkPopupShow(); + + // Clean-up and next test. + gBrowser.removeTab(gBrowser.selectedTab); + executeSoon(test5); + }); + }); + + tab.linkedBrowser.addEventListener("load", function test4bTabLBLoad(aEvent) { + tab.linkedBrowser.removeEventListener("load", test4bTabLBLoad, true); + + gBrowser.contentDocument.getElementById('s').click(); + }, true); + + gBrowser.selectedTab = tab; + gBrowser.selectedTab.linkedBrowser.loadURI(uri); +} + +/** + * In this test, we check that we can hide the popup by blurring the invalid + * element. + */ +function test5() +{ + let uri = "data:text/html,
"; + let tab = gBrowser.addTab(); + + gInvalidFormPopup.addEventListener("popupshown", function test5gIpopupShown() { + gInvalidFormPopup.removeEventListener("popupshown", test5gIpopupShown); + + let doc = gBrowser.contentDocument; + is(doc.activeElement, doc.getElementById('i'), + "First invalid element should be focused"); + + checkPopupShow(); + checkPopupMessage(doc); + + doc.getElementById('i').blur(); + + executeSoon(function () { + checkPopupHide(); + + // Clean-up and next test. + gBrowser.removeTab(gBrowser.selectedTab); + executeSoon(test6); + }); + }); + + tab.linkedBrowser.addEventListener("load", function test5TabLBLoad(aEvent) { + tab.linkedBrowser.removeEventListener("load", test5TabLBLoad, true); + + gBrowser.contentDocument.getElementById('s').click(); + }, true); + + gBrowser.selectedTab = tab; + gBrowser.selectedTab.linkedBrowser.loadURI(uri); +} + +/** + * In this test, we check that we can hide the popup by pressing TAB. + */ +function test6() +{ + let uri = "data:text/html,
"; + let tab = gBrowser.addTab(); + + gInvalidFormPopup.addEventListener("popupshown", function test6gIpopupShown() { + gInvalidFormPopup.removeEventListener("popupshown", test6gIpopupShown); + + let doc = gBrowser.contentDocument; + is(doc.activeElement, doc.getElementById('i'), + "First invalid element should be focused"); + + checkPopupShow(); + checkPopupMessage(doc); + + EventUtils.synthesizeKey("VK_TAB", {}); + + executeSoon(function () { + checkPopupHide(); + + // Clean-up and next test. + gBrowser.removeTab(gBrowser.selectedTab); + executeSoon(test7); + }); + }); + + tab.linkedBrowser.addEventListener("load", function test6TabLBLoad(aEvent) { + tab.linkedBrowser.removeEventListener("load", test6TabLBLoad, true); + + gBrowser.contentDocument.getElementById('s').click(); + }, true); + + gBrowser.selectedTab = tab; + gBrowser.selectedTab.linkedBrowser.loadURI(uri); +} + +/** + * In this test, we check that the popup will hide if we move to another tab. + */ +function test7() +{ + let uri = "data:text/html,
"; + let tab = gBrowser.addTab(); + + gInvalidFormPopup.addEventListener("popupshown", function test7gIpopupShown() { + gInvalidFormPopup.removeEventListener("popupshown", test7gIpopupShown); + + let doc = gBrowser.contentDocument; + is(doc.activeElement, doc.getElementById('i'), + "First invalid element should be focused"); + + checkPopupShow(); + checkPopupMessage(doc); + + // Create a new tab and move to it. + // Ftr, SeaMonkey doesn't support animation (yet). + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + + executeSoon(function() { + checkPopupHide(); + + // Clean-up and next test. + gBrowser.removeTab(gBrowser.selectedTab); + gBrowser.removeTab(gBrowser.selectedTab); + executeSoon(test8); + }); + }); + + tab.linkedBrowser.addEventListener("load", function test7TabLBLoad(aEvent) { + tab.linkedBrowser.removeEventListener("load", test7TabLBLoad, true); + + gBrowser.contentDocument.getElementById('s').click(); + }, true); + + gBrowser.selectedTab = tab; + gBrowser.selectedTab.linkedBrowser.loadURI(uri); +} + +/** + * In this test, we check that nothing happen (no focus nor popup) if the + * invalid form is submitted in another tab than the current focused one + * (submitted in background). + */ +function test8() +{ + let uri = "data:text/html,
"; + let tab = gBrowser.addTab(); + + gObserver.notifyInvalidSubmit = function() { + executeSoon(function() { + let doc = tab.linkedBrowser.contentDocument; + isnot(doc.activeElement, doc.getElementById('i'), + "We should not focus the invalid element when the form is submitted in background"); + + checkPopupHide(); + + // Clean-up + Services.obs.removeObserver(gObserver, "invalidformsubmit"); + gObserver.notifyInvalidSubmit = function () {}; + gBrowser.removeTab(tab); + + // Next test + executeSoon(test9); + }); + }; + + Services.obs.addObserver(gObserver, "invalidformsubmit"); + + tab.linkedBrowser.addEventListener("load", function test8TabLBLoad(aEvent) { + tab.linkedBrowser.removeEventListener("load", test8TabLBLoad, true); + + isnot(gBrowser.selectedTab, tab, + "This tab should have been loaded in background"); + + tab.linkedBrowser.contentDocument.getElementById('s').click(); + }, true); + + tab.linkedBrowser.loadURI(uri); +} + +/** + * In this test, we check that the author defined error message is shown. + */ +function test9() +{ + let uri = "data:text/html,
"; + let tab = gBrowser.addTab(); + + gInvalidFormPopup.addEventListener("popupshown", function test9gIpopupShown() { + gInvalidFormPopup.removeEventListener("popupshown", test9gIpopupShown); + + let doc = gBrowser.contentDocument; + is(doc.activeElement, doc.getElementById('i'), + "First invalid element should be focused"); + + checkPopupShow(); + + is(gInvalidFormPopup.firstChild.textContent, "foo", + "The panel should show the author defined error message"); + + // Clean-up and next test. + gBrowser.removeTab(gBrowser.selectedTab); + executeSoon(test10); + }); + + tab.linkedBrowser.addEventListener("load", function test9TabLBLoad(aEvent) { + tab.linkedBrowser.removeEventListener("load", test9TabLBLoad, true); + + gBrowser.contentDocument.getElementById('s').click(); + }, true); + + gBrowser.selectedTab = tab; + gBrowser.selectedTab.linkedBrowser.loadURI(uri); +} + +/** + * In this test, we check that the message is correctly updated when it changes. + */ +function test10() +{ + let uri = "data:text/html,
"; + let tab = gBrowser.addTab(); + + gInvalidFormPopup.addEventListener("popupshown", function test10gIpopupShown() { + gInvalidFormPopup.removeEventListener("popupshown", test10gIpopupShown); + + let doc = gBrowser.contentDocument; + let input = doc.getElementById('i'); + is(doc.activeElement, input, "First invalid element should be focused"); + + checkPopupShow(); + + is(gInvalidFormPopup.firstChild.textContent, input.validationMessage, + "The panel should show the current validation message"); + + input.addEventListener('input', function test10InputInput() { + input.removeEventListener('input', test10InputInput); + + executeSoon(function() { + // Now, the element suffers from another error, the message should have + // been updated. + is(gInvalidFormPopup.firstChild.textContent, input.validationMessage, + "The panel should show the current validation message"); + + // Clean-up and next test. + gBrowser.removeTab(gBrowser.selectedTab); + executeSoon(finish); + }); + }); + + EventUtils.synthesizeKey('f', {}); + }); + + tab.linkedBrowser.addEventListener("load", function test10TabLBLoad(aEvent) { + tab.linkedBrowser.removeEventListener("load", test10TabLBLoad, true); + + gBrowser.contentDocument.getElementById('s').click(); + }, true); + + gBrowser.selectedTab = tab; + gBrowser.selectedTab.linkedBrowser.loadURI(uri); +} diff --git a/comm/suite/browser/test/browser/browser_bug562649.js b/comm/suite/browser/test/browser/browser_bug562649.js new file mode 100644 index 0000000000..57524a4b82 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_bug562649.js @@ -0,0 +1,26 @@ +function test() { + const URI = "data:text/plain,bug562649"; + browserDOMWindow.openURI(makeURI(URI), + null, + Ci.nsIBrowserDOMWindow.OPEN_NEWTAB, + Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); + + is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI"); + is(gURLBar.value, URI, "location bar value matches test URI"); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.removeCurrentTab(); + is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI after switching tabs"); + is(gURLBar.value, URI, "location bar value matches test URI after switching tabs"); + + waitForExplicitFinish(); + gBrowser.selectedBrowser.addEventListener("load", function loadListener() { + gBrowser.selectedBrowser.removeEventListener("load", loadListener, true); + + is(gBrowser.userTypedValue, null, "userTypedValue is null as the page has loaded"); + is(gURLBar.value, URI, "location bar value matches test URI as the page has loaded"); + + gBrowser.removeCurrentTab(); + finish(); + }, true); +} diff --git a/comm/suite/browser/test/browser/browser_bug581947.js b/comm/suite/browser/test/browser/browser_bug581947.js new file mode 100644 index 0000000000..5ac81b1218 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_bug581947.js @@ -0,0 +1,95 @@ +function check(aElementName, aBarred, aType) { + let doc = gBrowser.contentDocument; + let tooltip = document.getElementById("aHTMLTooltip"); + let content = doc.getElementById('content'); + + let e = doc.createElement(aElementName); + content.appendChild(e); + + if (aType) { + e.type = aType; + } + + ok(!FillInHTMLTooltip(e), + "No tooltip should be shown when the element is valid"); + + e.setCustomValidity('foo'); + if (aBarred) { + ok(!FillInHTMLTooltip(e), + "No tooltip should be shown when the element is barred from constraint validation"); + } else { + ok(FillInHTMLTooltip(e), + e.tagName + " " +"A tooltip should be shown when the element isn't valid"); + } + + e.setAttribute('title', ''); + ok (!FillInHTMLTooltip(e), + "No tooltip should be shown if the title attribute is set"); + + e.removeAttribute('title'); + content.setAttribute('novalidate', ''); + ok (!FillInHTMLTooltip(e), + "No tooltip should be shown if the novalidate attribute is set on the form owner"); + content.removeAttribute('novalidate'); + + e.remove(); +} + +function todo_check(aElementName, aBarred) { + let doc = gBrowser.contentDocument; + let tooltip = document.getElementById("aHTMLTooltip"); + let content = doc.getElementById('content'); + + let e = doc.createElement(aElementName); + content.appendChild(e); + + let caught = false; + try { + e.setCustomValidity('foo'); + } catch (e) { + caught = true; + } + + todo(!caught, "setCustomValidity should exist for " + aElementName); + + e.remove(); +} + +function test() { + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function loadListener() { + gBrowser.selectedBrowser.removeEventListener("load", loadListener, true); + + let testData = [ + /* element name, barred */ + [ 'input', false, null], + [ 'textarea', false, null], + [ 'button', true, 'button'], + [ 'button', false, 'submit'], + [ 'select', false, null], + [ 'output', true, null], + [ 'fieldset', true, null], + [ 'object', true, null] + ]; + + for (let data of testData) { + check(data[0], data[1], data[2]); + } + + let todo_testData = [ + [ 'keygen', 'false' ] + ]; + + for (let data of todo_testData) { + todo_check(data[0], data[1]); + } + + gBrowser.removeCurrentTab(); + finish(); + }, true); + + content.location = + "data:text/html,
"; +} + diff --git a/comm/suite/browser/test/browser/browser_bug585511.js b/comm/suite/browser/test/browser/browser_bug585511.js new file mode 100644 index 0000000000..f6513cac5a --- /dev/null +++ b/comm/suite/browser/test/browser/browser_bug585511.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function test() { + is(getBrowser().tabs.length, 1, "one tab is open initially"); + is(getBrowser().browsers.length, getBrowser().tabs.length, ".browsers is in sync"); + + // Add several new tabs + let tab1 = getBrowser().addTab("http://mochi.test:8888/#1"); + let tab2 = getBrowser().addTab("http://mochi.test:8888/#2"); + let tab3 = getBrowser().addTab("http://mochi.test:8888/#3"); + is(getBrowser().tabs.length, 4, "four tabs are open"); + is(getBrowser().browsers.length, getBrowser().tabs.length, ".browsers is in sync"); + getBrowser().removeTab(tab2); + is(getBrowser().tabs.length, 3, "three tabs are open"); + is(getBrowser().browsers.length, getBrowser().tabs.length, ".browsers is in sync"); + getBrowser().removeTab(tab1); + is(getBrowser().tabs.length, 2, "two tabs are open"); + is(getBrowser().browsers.length, getBrowser().tabs.length, ".browsers is in sync"); + getBrowser().removeTab(tab3); + is(getBrowser().tabs.length, 1, "we've closed all our tabs"); + is(getBrowser().browsers.length, getBrowser().tabs.length, ".browsers is in sync"); +} diff --git a/comm/suite/browser/test/browser/browser_bug595507.js b/comm/suite/browser/test/browser/browser_bug595507.js new file mode 100644 index 0000000000..0bea8cabc6 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_bug595507.js @@ -0,0 +1,39 @@ +var gInvalidFormPopup = document.getElementById('invalid-form-popup'); +ok(gInvalidFormPopup, + "The browser should have a popup to show when a form is invalid"); + +/** + * Make sure that the form validation error message shows even if the form is in an iframe. + */ +function test() +{ + waitForExplicitFinish(); + + let uri = "data:text/html,
\""; + let tab = gBrowser.addTab(); + + gInvalidFormPopup.addEventListener("popupshown", function testgIpopupShown() { + gInvalidFormPopup.removeEventListener("popupshown", testgIpopupShown); + + let doc = gBrowser.contentDocument.getElementsByTagName('iframe')[0].contentDocument; + is(doc.activeElement, doc.getElementById('i'), + "First invalid element should be focused"); + + ok(gInvalidFormPopup.state == 'showing' || gInvalidFormPopup.state == 'open', + "The invalid form popup should be shown"); + + // Clean-up and next test. + gBrowser.removeTab(gBrowser.selectedTab, {animate: false}); + executeSoon(finish); + }); + + tab.linkedBrowser.addEventListener("load", function testTabLBLoad(aEvent) { + tab.linkedBrowser.removeEventListener("load", testTabLBLoad, true); + + gBrowser.contentDocument.getElementsByTagName('iframe')[0].contentDocument + .getElementById('s').click(); + }, true); + + gBrowser.selectedTab = tab; + gBrowser.selectedTab.linkedBrowser.loadURI(uri); +} diff --git a/comm/suite/browser/test/browser/browser_bug623155.js b/comm/suite/browser/test/browser/browser_bug623155.js new file mode 100644 index 0000000000..df8197a565 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_bug623155.js @@ -0,0 +1,136 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const REDIRECT_FROM = "https://example.com/browser/suite/browser/test/browser/" + + "redirect_bug623155.sjs"; + +const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host. + +function isRedirectedURISpec(aURISpec) { + return isRedirectedURI(Services.io.newURI(aURISpec)); +} + +function isRedirectedURI(aURI) { + // Compare only their before-hash portion. + return Services.io.newURI(REDIRECT_TO) + .equalsExceptRef(aURI); +} + +/* + Test. + +1. Load +https://example.com/browser/browser/base/content/test/redirect_bug623155.sjs#BG + in a background tab. + +2. The redirected URI is , which displayes a cert + error page. + +3. Switch the tab to foreground. + +4. Check the URLbar's value, expecting + +5. Load +https://example.com/browser/browser/base/content/test/redirect_bug623155.sjs#FG + in the foreground tab. + +6. The redirected URI is . And this is also + a cert-error page. + +7. Check the URLbar's value, expecting + +8. End. + + */ + +var gNewTab; + +function test() { + waitForExplicitFinish(); + + // Load a URI in the background. + gNewTab = gBrowser.addTab(REDIRECT_FROM + "#BG"); + gBrowser.getBrowserForTab(gNewTab) + .webProgress + .addProgressListener(gWebProgressListener, + Ci.nsIWebProgress + .NOTIFY_LOCATION); +} + +var gWebProgressListener = { + QueryInterface: function(aIID) { + if (aIID.equals(Ci.nsIWebProgressListener) || + aIID.equals(Ci.nsISupportsWeakReference) || + aIID.equals(Ci.nsISupports)) + return this; + throw Cr.NS_NOINTERFACE; + }, + + // --------------------------------------------------------------------------- + // NOTIFY_LOCATION mode should work fine without these methods. + // + //onStateChange: function() {}, + //onStatusChange: function() {}, + //onProgressChange: function() {}, + //onSecurityChange: function() {}, + //---------------------------------------------------------------------------- + + onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) { + if (!aRequest) { + // This is bug 673752, or maybe initial "about:blank". + return; + } + + ok(gNewTab, "There is a new tab."); + ok(isRedirectedURI(aLocation), + "onLocationChange catches only redirected URI."); + + if (aLocation.ref == "BG") { + // This is background tab's request. + isnot(gNewTab, gBrowser.selectedTab, "This is a background tab."); + } else if (aLocation.ref == "FG") { + // This is foreground tab's request. + is(gNewTab, gBrowser.selectedTab, "This is a foreground tab."); + } + else { + // We shonuld not reach here. + ok(false, "This URI hash is not expected:" + aLocation.ref); + } + + let isSelectedTab = (gNewTab == gBrowser.selectedTab); + setTimeout(delayed, 0, isSelectedTab); + } +}; + +function delayed(aIsSelectedTab) { + // Switch tab and confirm URL bar. + if (!aIsSelectedTab) { + gBrowser.selectedTab = gNewTab; + } + + ok(isRedirectedURISpec(content.location.href), + "The content area is redirected. aIsSelectedTab:" + aIsSelectedTab); + is(gURLBar.value, content.location.href, + "The URL bar shows the content URI. aIsSelectedTab:" + aIsSelectedTab); + + if (!aIsSelectedTab) { + // If this was a background request, go on a foreground request. + content.location = REDIRECT_FROM + "#FG"; + } + else { + // Othrewise, nothing to do remains. + finish(); + } +} + +/* Cleanup */ +registerCleanupFunction(function() { + if (gNewTab) { + gBrowser.getBrowserForTab(gNewTab) + .webProgress + .removeProgressListener(gWebProgressListener); + + gBrowser.removeTab(gNewTab); + } + gNewTab = null; +}); diff --git a/comm/suite/browser/test/browser/browser_ctrlTab.js b/comm/suite/browser/test/browser/browser_ctrlTab.js new file mode 100644 index 0000000000..aea07917a3 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_ctrlTab.js @@ -0,0 +1,197 @@ +function test() { + waitForExplicitFinish(); + + gBrowser.addTab(); + gBrowser.addTab(); + gBrowser.addTab(); + + assertTabs(4); + + ctrlTabTest([2] , 1, 0); + ctrlTabTest([2, 3, 1], 2, 2); + ctrlTabTest([] , 4, 2); + + { + let selectedIndex = gBrowser.tabContainer.selectedIndex; + pressCtrlTab(); + pressCtrlTab(true); + releaseCtrl(); + is(gBrowser.tabContainer.selectedIndex, selectedIndex, + "Ctrl+Tab -> Ctrl+Shift+Tab keeps the selected tab"); + } + + { // test for bug 445369 + let tabs = gBrowser.tabs.length; + pressCtrlTab(); + EventUtils.synthesizeKey("w", { ctrlKey: true }); + is(gBrowser.tabs.length, tabs - 1, "Ctrl+Tab -> Ctrl+W removes one tab"); + releaseCtrl(); + } + assertTabs(3); + + ctrlTabTest([2, 1, 0], 7, 1); + + { // test for bug 445369 + selectTabs([1, 2, 0]); + + let selectedTab = gBrowser.selectedTab; + let tabToRemove = gBrowser.tabs[1]; + + pressCtrlTab(); + pressCtrlTab(); + EventUtils.synthesizeKey("w", { ctrlKey: true }); + ok(!tabToRemove.parentNode, + "Ctrl+Tab*2 -> Ctrl+W removes the second most recently selected tab"); + + pressCtrlTab(true); + pressCtrlTab(true); + releaseCtrl(); + ok(gBrowser.selectedTab == selectedTab, + "Ctrl+Tab*2 -> Ctrl+W -> Ctrl+Shift+Tab*2 keeps the selected tab"); + } + assertTabs(2); + + ctrlTabTest([1], 1, 0); + + gBrowser.removeTab(gBrowser.tabContainer.lastChild); + + assertTabs(1); + + { // test for bug 445768 + let focusedWindow = document.commandDispatcher.focusedWindow; + let eventConsumed = true; + let detectKeyEvent = function (event) { + eventConsumed = event.defaultPrevented; + }; + document.addEventListener("keypress", detectKeyEvent); + pressCtrlTab(); + document.removeEventListener("keypress", detectKeyEvent); + ok(eventConsumed, "Ctrl+Tab consumed by the tabbed browser if one tab is open"); + is(focusedWindow.location, document.commandDispatcher.focusedWindow.location, + "Ctrl+Tab doesn't change focus if one tab is open"); + } + + gBrowser.addTab(); + gBrowser.addTab(); + gBrowser.addTab(); + + assertTabs(4); + selectTabs([0, 1, 2, 3]); + pressCtrlTab(); + ctrlTab.panel.addEventListener("popupshown", stickyTests); + + function stickyTests() { + ctrlTab.panel.removeEventListener("popupshown", stickyTests); + + EventUtils.synthesizeKey("f", { ctrlKey: true }); + is(document.activeElement, ctrlTab.searchField.inputField, + "Ctrl+Tab -> Ctrl+F focuses the panel's search field"); + + releaseCtrl(); + ok(isOpen(), + "panel is sticky after focusing the search field and releasing the Ctrl key"); + + EventUtils.synthesizeKey("f", {}); + EventUtils.synthesizeKey("o", {}); + EventUtils.synthesizeKey("o", {}); + is(ctrlTab.searchField.value, "foo", + "text entered into search field"); + + EventUtils.synthesizeKey("VK_RETURN", {}); + ok(isOpen(), + "Enter key kicks pending search off; the panel stays open as there's no match"); + is(ctrlTab.searchField.value, "foo", + "search field value persists after Enter pressed"); + + EventUtils.synthesizeKey("VK_ESCAPE", {}); + is(ctrlTab.searchField.value, "", + "ESC key clears the search field"); + ok(isOpen(), + "Clearing the search field with ESC keeps the panel open"); + + // blur the search field + EventUtils.synthesizeKey("VK_TAB", {}); + isnot(document.activeElement, ctrlTab.searchField.inputField, + "Tab key blurs the panel's search field"); + + // advance selection and close panel + EventUtils.synthesizeKey("VK_TAB", {}); + EventUtils.synthesizeKey("VK_TAB", {}); + EventUtils.synthesizeKey("VK_RETURN", {}); + ok(!isOpen(), + "Enter key closes the panel"); + is(gBrowser.tabContainer.selectedIndex, 1, + "Tab key advances the selection while the panel is sticky"); + + gBrowser.removeCurrentTab(); + gBrowser.removeCurrentTab(); + gBrowser.removeCurrentTab(); + assertTabs(1); + finish(); + } + + + /* private utility functions */ + + function pressCtrlTab(aShiftKey) { + EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: !!aShiftKey }); + } + + function releaseCtrl() { + EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" }); + } + + function isOpen() { + return ctrlTab.panel.state == "showing" || ctrlTab.panel.state == "open"; + } + + function assertTabs(aTabs) { + var tabs = gBrowser.tabs.length; + if (tabs != aTabs) { + while (gBrowser.tabs.length > 1) + gBrowser.removeCurrentTab(); + throw "expected " + aTabs + " open tabs, got " + tabs; + } + } + + function selectTabs(tabs) { + tabs.forEach(function (index) { + gBrowser.selectedTab = gBrowser.tabs[index]; + }); + } + + function ctrlTabTest(tabsToSelect, tabTimes, expectedIndex) { + selectTabs(tabsToSelect); + + var indexStart = gBrowser.tabContainer.selectedIndex; + var tabCount = gBrowser.tabs.length; + var normalized = tabTimes % tabCount; + var where = normalized == 1 ? "back to the previously selected tab" : + normalized + " tabs back in most-recently-selected order"; + + for (let i = 0; i < tabTimes; i++) { + pressCtrlTab(); + + if (tabCount > 2) + is(gBrowser.tabContainer.selectedIndex, indexStart, + "Selected tab doesn't change while tabbing"); + } + + if (tabCount > 2) { + ok(isOpen(), + "With " + tabCount + " tabs open, Ctrl+Tab opens the preview panel"); + + releaseCtrl(); + + ok(!isOpen(), + "Releasing Ctrl closes the preview panel"); + } else { + ok(!isOpen(), + "With " + tabCount + " tabs open, Ctrl+Tab doesn't open the preview panel"); + } + + is(gBrowser.tabContainer.selectedIndex, expectedIndex, + "With "+ tabCount +" tabs open and tab " + indexStart + + " selected, Ctrl+Tab*" + tabTimes + " goes " + where); + } +} diff --git a/comm/suite/browser/test/browser/browser_fayt.js b/comm/suite/browser/test/browser/browser_fayt.js new file mode 100644 index 0000000000..8522bc366b --- /dev/null +++ b/comm/suite/browser/test/browser/browser_fayt.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function test() { + var tab1 = gBrowser.addTab("data:text/html;charset=utf-8,

this is some dummy text

"); + var tab2 = gBrowser.addTab("data:text/html;charset=utf-8,

this is some random text

"); + + gBrowser.getBrowserForTab(tab2).addEventListener("load", runTest, true); + waitForExplicitFinish(); + + function runTest() { + gBrowser.getBrowserForTab(tab2).removeEventListener("load", runTest, true); + + gBrowser.selectedTab = tab2; + is(gBrowser.fastFind.find("random", false), Ci.nsITypeAheadFind.FIND_FOUND, "FAYT found the random text"); + gBrowser.selectedTab = tab1; + is(gBrowser.fastFind.find("dummy", false), Ci.nsITypeAheadFind.FIND_FOUND, "FAYT found the dummy text"); + + gBrowser.removeTab(tab2); + gBrowser.removeTab(tab1); + finish(); + } +} + diff --git a/comm/suite/browser/test/browser/browser_notification_tab_switching.js b/comm/suite/browser/test/browser/browser_notification_tab_switching.js new file mode 100644 index 0000000000..2cbdaa3147 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_notification_tab_switching.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +var tab; +var notification; +var notificationURL = "http://example.org/browser/suite/browser/test/browser/file_dom_notifications.html"; +var newWindowOpenedFromTab; + +function test () { + waitForExplicitFinish(); + + let pm = Services.perms; + registerCleanupFunction(function() { + pm.remove(makeURI(notificationURL), "desktop-notification"); + gBrowser.removeTab(tab); + window.restore(); + }); + + pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION); + + tab = gBrowser.addTab(notificationURL); + tab.linkedBrowser.addEventListener("load", onLoad, true); +} + +function onLoad() { + isnot(gBrowser.selectedTab, tab, "Notification page loaded as a background tab"); + tab.linkedBrowser.removeEventListener("load", onLoad, true); + let win = tab.linkedBrowser.contentWindow.wrappedJSObject; + win.newWindow = win.open("about:blank", "", "height=100,width=100"); + newWindowOpenedFromTab = win.newWindow; + win.newWindow.addEventListener("load", function() { + info("new window loaded"); + win.newWindow.addEventListener("blur", function b() { + info("new window got blur"); + win.newWindow.removeEventListener("blur", b); + notification = win.showNotification1(); + win.newWindow.addEventListener("focus", onNewWindowFocused); + notification.addEventListener("show", onAlertShowing); + }); + + function waitUntilNewWindowHasFocus() { + if (!win.newWindow.document.hasFocus()) { + setTimeout(waitUntilNewWindowHasFocus, 50); + } else { + // Focus another window so that new window gets blur event. + gBrowser.selectedBrowser.contentWindow.focus(); + } + } + win.newWindow.focus(); + waitUntilNewWindowHasFocus(); + }); +} + +function onAlertShowing() { + info("Notification alert showing"); + notification.removeEventListener("show", onAlertShowing); + + let alertWindow = findChromeWindowByURI("chrome://global/content/alerts/alert.xul"); + if (!alertWindow) { + todo(false, "Notifications don't use XUL windows on all platforms."); + notification.close(); + newWindowOpenedFromTab.close(); + finish(); + return; + } + gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect); + EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"), {}, alertWindow); + info("Clicked on notification"); + alertWindow.close(); +} + +function onNewWindowFocused(event) { + event.target.close(); + isnot(gBrowser.selectedTab, tab, "Notification page loaded as a background tab"); + // Using timeout to test that something do *not* happen! + setTimeout(openSecondNotification, 50); +} + +function openSecondNotification() { + isnot(gBrowser.selectedTab, tab, "Notification page loaded as a background tab"); + let win = tab.linkedBrowser.contentWindow.wrappedJSObject; + notification = win.showNotification2(); + notification.addEventListener("show", onAlertShowing); +} + +function onTabSelect() { + gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect); + is(gBrowser.selectedBrowser.contentWindow.location.href, notificationURL, + "Notification tab should be selected."); + + finish(); +} diff --git a/comm/suite/browser/test/browser/browser_pageInfo.js b/comm/suite/browser/test/browser/browser_pageInfo.js new file mode 100644 index 0000000000..399301a784 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_pageInfo.js @@ -0,0 +1,38 @@ +function test() { + waitForExplicitFinish(); + + var pageInfo; + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function loadListener() { + gBrowser.selectedBrowser.removeEventListener("load", loadListener, true); + + Services.obs.addObserver(observer, "page-info-dialog-loaded"); + pageInfo = BrowserPageInfo(); + }, true); + content.location = + "https://example.com/browser/suite/browser/test/browser/feed_tab.html"; + + function observer(win, topic, data) { + Services.obs.removeObserver(observer, "page-info-dialog-loaded"); + handlePageInfo(); + } + + function handlePageInfo() { + ok(pageInfo.document.getElementById("feedPanel"), "Feed panel"); + let feedListbox = pageInfo.document.getElementById("feedListbox"); + ok(feedListbox, "Feed list"); + + var feedRowsNum = feedListbox.getRowCount(); + is(feedRowsNum, 3, "Number of feeds listed"); + + for (var i = 0; i < feedRowsNum; i++) { + let feedItem = feedListbox.getItemAtIndex(i); + is(feedItem.getAttribute("name"), i + 1, "Feed name"); + } + + pageInfo.close(); + gBrowser.removeCurrentTab(); + finish(); + } +} diff --git a/comm/suite/browser/test/browser/browser_page_style_menu.js b/comm/suite/browser/test/browser/browser_page_style_menu.js new file mode 100644 index 0000000000..88ce97da0b --- /dev/null +++ b/comm/suite/browser/test/browser/browser_page_style_menu.js @@ -0,0 +1,67 @@ +function test() { + waitForExplicitFinish(); + + var tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + tab.linkedBrowser.addEventListener("load", function loadListener() { + tab.linkedBrowser.removeEventListener("load", loadListener, true); + checkPageStyleMenu(); + }, true); + let rootDir = getRootDirectory(gTestPath); + content.location = rootDir + "page_style_sample.html"; +} + +function checkPageStyleMenu() { + var menupopup = document.getElementById("menu_UseStyleSheet") + .getElementsByTagName("menupopup")[0]; + stylesheetFillPopup(menupopup); + + var items = []; + var current = menupopup.getElementsByTagName("menuitem")[1]; + while (current.nextSibling) { + current = current.nextSibling; + items.push(current); + } + + var validLinks = 0; + Array.from(content.document.getElementsByTagName("link")).forEach(function (link) { + var title = link.getAttribute("title"); + var rel = link.getAttribute("rel"); + var media = link.getAttribute("media"); + var idstring = "link " + (title ? title : "without title and") + + " with rel=\"" + rel + "\"" + + (media ? " and media=\"" + media + "\"" : ""); + + var item = items.filter(item => item.getAttribute("label") == title); + var found = item.length == 1; + var checked = found && (item[0].getAttribute("checked") == "true"); + + switch (link.getAttribute("data-state")) { + case "0": + ok(!found, idstring + " does not show up in page style menu"); + break; + case "0-todo": + validLinks++; + todo(!found, idstring + " should not show up in page style menu"); + ok(!checked, idstring + " is not selected"); + break; + case "1": + validLinks++; + ok(found, idstring + " shows up in page style menu"); + ok(!checked, idstring + " is not selected"); + break; + case "2": + validLinks++; + ok(found, idstring + " shows up in page style menu"); + ok(checked, idstring + " is selected"); + break; + default: + throw "data-state attribute is missing or has invalid value"; + } + }); + + is(validLinks, items.length, "all valid links found"); + + gBrowser.removeCurrentTab(); + finish(); +} diff --git a/comm/suite/browser/test/browser/browser_popupNotification.js b/comm/suite/browser/test/browser/browser_popupNotification.js new file mode 100644 index 0000000000..da88243e66 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_popupNotification.js @@ -0,0 +1,782 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + + ok(PopupNotifications, "PopupNotifications object exists"); + ok(PopupNotifications.panel, "PopupNotifications panel exists"); + + registerCleanupFunction(cleanUp); + + runNextTest(); +} + +function cleanUp() { + for (var topic in gActiveObservers) + Services.obs.removeObserver(gActiveObservers[topic], topic); + for (var eventName in gActiveListeners) + PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName]); +} + +var gActiveListeners = {}; +var gActiveObservers = {}; +var gShownState = {}; + +function runNextTest() { + let nextTest = tests[gTestIndex]; + + function goNext() { + if (++gTestIndex == tests.length) + executeSoon(finish); + else + executeSoon(runNextTest); + } + + function addObserver(topic) { + function observer() { + Services.obs.removeObserver(observer, "PopupNotifications-" + topic); + delete gActiveObservers["PopupNotifications-" + topic]; + + info("[Test #" + gTestIndex + "] observer for " + topic + " called"); + nextTest[topic](); + goNext(); + } + Services.obs.addObserver(observer, "PopupNotifications-" + topic); + gActiveObservers["PopupNotifications-" + topic] = observer; + } + + if (nextTest.backgroundShow) { + addObserver("backgroundShow"); + } else if (nextTest.updateNotShowing) { + addObserver("updateNotShowing"); + } else { + doOnPopupEvent("popupshowing", function () { + info("[Test #" + gTestIndex + "] popup showing"); + }); + doOnPopupEvent("popupshown", function () { + gShownState[gTestIndex] = true; + info("[Test #" + gTestIndex + "] popup shown"); + nextTest.onShown(this); + }); + + // We allow multiple onHidden functions to be defined in an array. They're + // called in the order they appear. + let onHiddenArray = nextTest.onHidden instanceof Array ? + nextTest.onHidden : + [nextTest.onHidden]; + doOnPopupEvent("popuphidden", function () { + if (!gShownState[gTestIndex]) { + // This is expected to happen for test 9, so let's not treat it as a failure. + info("Popup from test " + gTestIndex + " was hidden before its popupshown fired"); + } + + let onHidden = onHiddenArray.shift(); + info("[Test #" + gTestIndex + "] popup hidden (" + onHiddenArray.length + " hides remaining)"); + executeSoon(function () { + onHidden.call(nextTest, this); + if (!onHiddenArray.length) + goNext(); + }); + }, onHiddenArray.length); + info("[Test #" + gTestIndex + "] added listeners; panel state: " + PopupNotifications.isPanelOpen); + } + + info("[Test #" + gTestIndex + "] running test"); + nextTest.run(); +} + +function doOnPopupEvent(eventName, callback, numExpected) { + gActiveListeners[eventName] = function (event) { + if (event.target != PopupNotifications.panel) + return; + if (typeof(numExpected) === "number") + numExpected--; + if (!numExpected) { + PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName]); + delete gActiveListeners[eventName]; + } + + callback.call(PopupNotifications.panel); + } + PopupNotifications.panel.addEventListener(eventName, gActiveListeners[eventName]); +} + +var gTestIndex = 0; +var gNewTab; + +function basicNotification() { + var self = this; + this.browser = gBrowser.selectedBrowser; + this.id = "test-notification-" + gTestIndex; + this.message = "This is popup notification " + this.id + " from test " + gTestIndex; + this.anchorID = null; + this.mainAction = { + label: "Main Action", + accessKey: "M", + callback: function () { + self.mainActionClicked = true; + } + }; + this.secondaryActions = [ + { + label: "Secondary Action", + accessKey: "S", + callback: function () { + self.secondaryActionClicked = true; + } + } + ]; + this.options = { + eventCallback: function (eventName) { + switch (eventName) { + case "dismissed": + self.dismissalCallbackTriggered = true; + break; + case "shown": + self.shownCallbackTriggered = true; + break; + case "removed": + self.removedCallbackTriggered = true; + break; + } + } + }; + this.addOptions = function(options) { + for (let [name, value] of Object.entries(options)) + self.options[name] = value; + } +} + +var wrongBrowserNotificationObject = new basicNotification(); +var wrongBrowserNotification; + +var tests = [ + { // Test #0 + run: function () { + this.notifyObj = new basicNotification(); + showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerMainCommand(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.mainActionClicked, "mainAction was clicked"); + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + { // Test #1 + run: function () { + this.notifyObj = new basicNotification(); + showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerSecondaryCommand(popup, 0); + }, + onHidden: function (popup) { + ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked"); + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + { // Test #2 + run: function () { + this.notifyObj = new basicNotification(); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // test opening a notification for a background browser + { // Test #3 + run: function () { + gNewTab = gBrowser.addTab("about:blank"); + isnot(gBrowser.selectedTab, gNewTab, "new tab isn't selected"); + wrongBrowserNotificationObject.browser = gBrowser.getBrowserForTab(gNewTab); + wrongBrowserNotification = showNotification(wrongBrowserNotificationObject); + }, + backgroundShow: function () { + is(PopupNotifications.isPanelOpen, false, "panel isn't open"); + ok(!wrongBrowserNotificationObject.mainActionClicked, "main action wasn't clicked"); + ok(!wrongBrowserNotificationObject.secondaryActionClicked, "secondary action wasn't clicked"); + ok(!wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback wasn't called"); + } + }, + // now select that browser and test to see that the notification appeared + { // Test #4 + run: function () { + this.oldSelectedTab = gBrowser.selectedTab; + gBrowser.selectedTab = gNewTab; + }, + onShown: function (popup) { + checkPopup(popup, wrongBrowserNotificationObject); + is(PopupNotifications.isPanelOpen, true, "isPanelOpen getter doesn't lie"); + + // switch back to the old browser + gBrowser.selectedTab = this.oldSelectedTab; + }, + onHidden: function (popup) { + // actually remove the notification to prevent it from reappearing + ok(wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback triggered due to tab switch"); + wrongBrowserNotification.remove(); + ok(wrongBrowserNotificationObject.removedCallbackTriggered, "removed callback triggered"); + wrongBrowserNotification = null; + } + }, + // test that the removed notification isn't shown on browser re-select + { // Test #5 + run: function () { + gBrowser.selectedTab = gNewTab; + }, + updateNotShowing: function () { + is(PopupNotifications.isPanelOpen, false, "panel isn't open"); + gBrowser.removeTab(gNewTab); + } + }, + // Test that two notifications with the same ID result in a single displayed + // notification. + { // Test #6 + run: function () { + this.notifyObj = new basicNotification(); + // Show the same notification twice + this.notification1 = showNotification(this.notifyObj); + this.notification2 = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + this.notification2.remove(); + }, + onHidden: function (popup) { + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test that two notifications with different IDs are displayed + { // Test #7 + run: function () { + this.testNotif1 = new basicNotification(); + this.testNotif1.message += " 1"; + showNotification(this.testNotif1); + this.testNotif2 = new basicNotification(); + this.testNotif2.message += " 2"; + this.testNotif2.id += "-2"; + showNotification(this.testNotif2); + }, + onShown: function (popup) { + is(popup.childNodes.length, 2, "two notifications are shown"); + // Trigger the main command for the first notification, and the secondary + // for the second. Need to do mainCommand first since the secondaryCommand + // triggering is async. + triggerMainCommand(popup); + is(popup.childNodes.length, 1, "only one notification left"); + triggerSecondaryCommand(popup, 0); + }, + onHidden: function (popup) { + ok(this.testNotif1.mainActionClicked, "main action #1 was clicked"); + ok(!this.testNotif1.secondaryActionClicked, "secondary action #1 wasn't clicked"); + ok(!this.testNotif1.dismissalCallbackTriggered, "dismissal callback #1 wasn't called"); + + ok(!this.testNotif2.mainActionClicked, "main action #2 wasn't clicked"); + ok(this.testNotif2.secondaryActionClicked, "secondary action #2 was clicked"); + ok(!this.testNotif2.dismissalCallbackTriggered, "dismissal callback #2 wasn't called"); + } + }, + // Test notification without mainAction + { // Test #8 + run: function () { + this.notifyObj = new basicNotification(); + this.notifyObj.mainAction = null; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function (popup) { + this.notification.remove(); + } + }, + // Test two notifications with different anchors + { // Test #9 + run: function () { + this.notifyObj = new basicNotification(); + this.firstNotification = showNotification(this.notifyObj); + this.notifyObj2 = new basicNotification(); + this.notifyObj2.id += "-2"; + this.notifyObj2.anchorID = "addons-notification-icon"; + // Second showNotification() overrides the first + this.secondNotification = showNotification(this.notifyObj2); + }, + onShown: function (popup) { + // This also checks that only one element is shown. + checkPopup(popup, this.notifyObj2); + is(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor shouldn't be visible"); + dismissNotification(popup); + }, + onHidden: [ + // The second showing triggers a popuphidden event that we should ignore. + function (popup) {}, + function (popup) { + // Remove the notifications + this.firstNotification.remove(); + this.secondNotification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered"); + } + ] + }, + // Test optional params + { // Test #10 + run: function () { + this.notifyObj = new basicNotification(); + this.notifyObj.secondaryActions = undefined; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test that icons appear + { // Test #11 + run: function () { + this.notifyObj = new basicNotification(); + this.notifyObj.id = "geolocation"; + this.notifyObj.anchorID = "geo-notification-icon"; + // Fake these options (with arbitrary values), + // to avoid strict warnings in SeaMonkey notification constructor. + this.notifyObj.addOptions({ + value: "Learn More...", + href: "http://www.seamonkey-project.org/doc/2.0/geolocation" + }); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + isnot(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor should be visible"); + dismissNotification(popup); + }, + onHidden: function (popup) { + let icon = document.getElementById("geo-notification-icon"); + isnot(icon.boxObject.width, 0, + "geo anchor should be visible after dismissal"); + this.notification.remove(); + is(icon.boxObject.width, 0, + "geo anchor should not be visible after removal"); + } + }, + // Test that persistence allows the notification to persist across reloads + { // Test #12 + run: function () { + this.oldSelectedTab = gBrowser.selectedTab; + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + + let self = this; + loadURI("http://example.com/", function() { + self.notifyObj = new basicNotification(); + self.notifyObj.addOptions({ + persistence: 2 + }); + self.notification = showNotification(self.notifyObj); + }); + }, + onShown: function (popup) { + this.complete = false; + + let self = this; + loadURI("http://example.org/", function() { + loadURI("http://example.com/", function() { + + // Next load will remove the notification + self.complete = true; + + loadURI("http://example.org/"); + }); + }); + }, + onHidden: function (popup) { + ok(this.complete, "Should only have hidden the notification after 3 page loads"); + ok(this.notifyObj.removedCallbackTriggered, "removal callback triggered"); + gBrowser.removeTab(gBrowser.selectedTab); + gBrowser.selectedTab = this.oldSelectedTab; + } + }, + // Test that a timeout allows the notification to persist across reloads + { // Test #13 + run: function () { + this.oldSelectedTab = gBrowser.selectedTab; + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + + let self = this; + loadURI("http://example.com/", function() { + self.notifyObj = new basicNotification(); + // Set a timeout of 10 minutes that should never be hit + self.notifyObj.addOptions({ + timeout: Date.now() + 600000 + }); + self.notification = showNotification(self.notifyObj); + }); + }, + onShown: function (popup) { + this.complete = false; + + let self = this; + loadURI("http://example.org/", function() { + loadURI("http://example.com/", function() { + + // Next load will hide the notification + self.notification.options.timeout = Date.now() - 1; + self.complete = true; + + loadURI("http://example.org/"); + }); + }); + }, + onHidden: function (popup) { + ok(this.complete, "Should only have hidden the notification after the timeout was passed"); + this.notification.remove(); + gBrowser.removeTab(gBrowser.selectedTab); + gBrowser.selectedTab = this.oldSelectedTab; + } + }, + // Test that setting persistWhileVisible allows a visible notification to + // persist across location changes + { // Test #14 + run: function () { + this.oldSelectedTab = gBrowser.selectedTab; + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + + let self = this; + loadURI("http://example.com/", function() { + self.notifyObj = new basicNotification(); + self.notifyObj.addOptions({ + persistWhileVisible: true + }); + self.notification = showNotification(self.notifyObj); + }); + }, + onShown: function (popup) { + this.complete = false; + + let self = this; + loadURI("http://example.org/", function() { + loadURI("http://example.com/", function() { + + // Notification should persist across location changes + self.complete = true; + dismissNotification(popup); + }); + }); + }, + onHidden: function (popup) { + ok(this.complete, "Should only have hidden the notification after it was dismissed"); + this.notification.remove(); + gBrowser.removeTab(gBrowser.selectedTab); + gBrowser.selectedTab = this.oldSelectedTab; + } + }, + // Test that nested icon nodes correctly activate popups + { // Test #15 + run: function() { + // Add a temporary box as the anchor with a button + this.box = document.createElement("box"); + PopupNotifications.iconBox.appendChild(this.box); + + let button = document.createElement("button"); + button.setAttribute("label", "Please click me!"); + this.box.appendChild(button); + + // The notification should open up on the box + this.notifyObj = new basicNotification(); + this.notifyObj.anchorID = this.box.id = "nested-box"; + this.notifyObj.addOptions({dismissed: true}); + this.notification = showNotification(this.notifyObj); + + EventUtils.synthesizeMouse(button, 1, 1, {}); + }, + onShown: function(popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function(popup) { + this.notification.remove(); + this.box.remove(); + } + }, + // Test that popupnotifications without popups have anchor icons shown + { // Test #16 + run: function() { + let notifyObj = new basicNotification(); + notifyObj.anchorID = "geo-notification-icon"; + notifyObj.addOptions({neverShow: true}); + showNotification(notifyObj); + }, + updateNotShowing: function() { + isnot(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor should be visible"); + } + }, + // Test notification "Not Now" menu item + { // Test #17 + run: function () { + this.notifyObj = new basicNotification(); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerSecondaryCommand(popup, 1); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test notification close button + { // Test #18 + run: function () { + this.notifyObj = new basicNotification(); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + EventUtils.synthesizeMouseAtCenter(notification.closebutton, {}); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test notification when chrome is hidden + { // Test #19 + run: function () { + window.locationbar.visible = false; + this.notifyObj = new basicNotification(); + this.notification = showNotification(this.notifyObj); + window.locationbar.visible = true; + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + is(popup.anchorNode.className, "tabbrowser-tab", "notification anchored to tab"); + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test notification is removed when dismissed if removeOnDismissal is true + { // Test #20 + run: function () { + this.notifyObj = new basicNotification(); + this.notifyObj.addOptions({ + removeOnDismissal: true + }); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test multiple notification icons are shown + { // Test #21 + run: function () { + this.notifyObj1 = new basicNotification(); + this.notifyObj1.id += "_1"; + this.notifyObj1.anchorID = "default-notification-icon"; + this.notification1 = showNotification(this.notifyObj1); + + this.notifyObj2 = new basicNotification(); + this.notifyObj2.id += "_2"; + this.notifyObj2.anchorID = "geo-notification-icon"; + this.notification2 = showNotification(this.notifyObj2); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj2); + + // check notifyObj1 anchor icon is showing + isnot(document.getElementById("default-notification-icon").boxObject.width, 0, + "default anchor should be visible"); + // check notifyObj2 anchor icon is showing + isnot(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor should be visible"); + + dismissNotification(popup); + }, + onHidden: [ + function (popup) { + }, + function (popup) { + this.notification1.remove(); + ok(this.notifyObj1.removedCallbackTriggered, "removed callback triggered"); + + this.notification2.remove(); + ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered"); + } + ] + }, + // Test that multiple notification icons are removed when switching tabs + { // Test #22 + run: function () { + // show the notification on old tab. + this.notifyObjOld = new basicNotification(); + this.notifyObjOld.anchorID = "default-notification-icon"; + this.notificationOld = showNotification(this.notifyObjOld); + + // switch tab + this.oldSelectedTab = gBrowser.selectedTab; + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + + // show the notification on new tab. + this.notifyObjNew = new basicNotification(); + this.notifyObjNew.anchorID = "geo-notification-icon"; + this.notificationNew = showNotification(this.notifyObjNew); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObjNew); + + // check notifyObjOld anchor icon is removed + is(document.getElementById("default-notification-icon").boxObject.width, 0, + "default anchor shouldn't be visible"); + // check notifyObjNew anchor icon is showing + isnot(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor should be visible"); + + dismissNotification(popup); + }, + onHidden: [ + function (popup) { + }, + function (popup) { + this.notificationNew.remove(); + gBrowser.removeTab(gBrowser.selectedTab); + + gBrowser.selectedTab = this.oldSelectedTab; + this.notificationOld.remove(); + } + ] + } +]; + +function showNotification(notifyObj) { + return PopupNotifications.show(notifyObj.browser, + notifyObj.id, + notifyObj.message, + notifyObj.anchorID, + notifyObj.mainAction, + notifyObj.secondaryActions, + notifyObj.options); +} + +function checkPopup(popup, notificationObj) { + info("[Test #" + gTestIndex + "] checking popup"); + + ok(notificationObj.shownCallbackTriggered, "shown callback was triggered"); + + let notifications = popup.childNodes; + is(notifications.length, 1, "only one notification displayed"); + let notification = notifications[0]; + let icon = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-icon"); + if (notificationObj.id == "geolocation") { + isnot(icon.boxObject.width, 0, "icon for geo displayed"); + is(popup.anchorNode.className, "notification-anchor-icon", "notification anchored to icon"); + } + is(notification.getAttribute("label"), notificationObj.message, "message matches"); + is(notification.id, notificationObj.id + "-notification", "id matches"); + if (notificationObj.mainAction) { + is(notification.getAttribute("buttonlabel"), notificationObj.mainAction.label, "main action label matches"); + is(notification.getAttribute("buttonaccesskey"), notificationObj.mainAction.accessKey, "main action accesskey matches"); + } + let actualSecondaryActions = notification.childNodes; + let secondaryActions = notificationObj.secondaryActions || []; + let actualSecondaryActionsCount = actualSecondaryActions.length; + if (secondaryActions.length) { + let lastChild = actualSecondaryActions.item(actualSecondaryActions.length - 1); + is(lastChild.tagName, "menuseparator", "menuseparator exists"); + actualSecondaryActionsCount--; + } + is(actualSecondaryActionsCount, secondaryActions.length, actualSecondaryActions.length + " secondary actions"); + secondaryActions.forEach(function (a, i) { + is(actualSecondaryActions[i].getAttribute("label"), a.label, "label for secondary action " + i + " matches"); + is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey, "accessKey for secondary action " + i + " matches"); + }); +} + +function triggerMainCommand(popup) { + info("[Test #" + gTestIndex + "] triggering main command"); + let notifications = popup.childNodes; + ok(notifications.length > 0, "at least one notification displayed"); + let notification = notifications[0]; + EventUtils.synthesizeMouseAtCenter(notification.button, {}); +} + +function triggerSecondaryCommand(popup, index) { + info("[Test #" + gTestIndex + "] triggering secondary command"); + let notifications = popup.childNodes; + ok(notifications.length > 0, "at least one notification displayed"); + let notification = notifications[0]; + info("Triggering secondary command for notification " + notification.id); + notification.button.nextSibling.nextSibling.focus(); + + popup.addEventListener("popupshown", function triggerPopupShown() { + popup.removeEventListener("popupshown", triggerPopupShown); + + // Press down until the desired command is selected + for (let i = 0; i <= index; i++) + EventUtils.synthesizeKey("VK_DOWN", {}); + + // Activate + EventUtils.synthesizeKey("VK_RETURN", {}); + }); + + // One down event to open the popup + EventUtils.synthesizeKey("VK_DOWN", { altKey: AppConstants.platform != "macosx" }); +} + +function loadURI(uri, callback) { + if (callback) { + gBrowser.addEventListener("load", function loadURIgBLoad() { + // Ignore the about:blank load + if (gBrowser.currentURI.spec != uri) + return; + + gBrowser.removeEventListener("load", loadURIgBLoad, true); + + callback(); + }, true); + } + gBrowser.loadURI(uri); +} + +function dismissNotification(popup) { + info("[Test #" + gTestIndex + "] dismissing notification"); + executeSoon(function () { + EventUtils.synthesizeKey("VK_ESCAPE", {}); + }); +} diff --git a/comm/suite/browser/test/browser/browser_privatebrowsing_protocolhandler.js b/comm/suite/browser/test/browser/browser_privatebrowsing_protocolhandler.js new file mode 100644 index 0000000000..2fc5392dca --- /dev/null +++ b/comm/suite/browser/test/browser/browser_privatebrowsing_protocolhandler.js @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This test makes sure that the web pages can't register protocol handlers +// inside the private browsing mode. + +function test() { + // initialization + waitForExplicitFinish(); + let windowsToClose = []; + let notificationValue = "Protocol Registration: testprotocol"; + let testURI = "http://example.com/browser/" + + "suite/browser/test/browser/browser_privatebrowsing_protocolhandler_page.html"; + + function doTest(aIsPrivateMode, aWindow, aCallback) { + aWindow.getBrowser().selectedBrowser.addEventListener("load", function onLoad() { + aWindow.getBrowser().selectedBrowser.removeEventListener("load", onLoad, true); + + setTimeout(function() { + let notificationBox = aWindow.getBrowser().getNotificationBox(); + let notification = notificationBox.getNotificationWithValue(notificationValue); + + if (aIsPrivateMode) { + // Make sure the notification is correctly displayed without a remember control + ok(!notification, "Notification box should not be displayed inside of private browsing mode"); + } else { + // Make sure the notification is correctly displayed with a remember control + ok(notification, "Notification box should be displaying outside of private browsing mode"); + } + + aCallback(); + }, 100); // remember control is added in a setTimeout(0) call + }, true); + + aWindow.getBrowser().selectedBrowser.loadURI(testURI); + } + + function testOnWindow(aOptions, aCallback) { + whenNewWindowLoaded(aOptions, function(aWin) { + windowsToClose.push(aWin); + // execute should only be called when need, like when you are opening + // web pages on the test. If calling executeSoon() is not necesary, then + // call whenNewWindowLoaded() instead of testOnWindow() on your test. + executeSoon(() => aCallback(aWin)); + }); + }; + + // this function is called after calling finish() on the test. + registerCleanupFunction(function() { + windowsToClose.forEach(function(aWin) { + aWin.close(); + }); + }); + + // test first when not on private mode + testOnWindow({}, function(aWin) { + doTest(false, aWin, function() { + // then test when on private mode + testOnWindow({private: true}, function(aWin) { + doTest(true, aWin, finish); + }); + }); + }); +} diff --git a/comm/suite/browser/test/browser/browser_privatebrowsing_protocolhandler_page.html b/comm/suite/browser/test/browser/browser_privatebrowsing_protocolhandler_page.html new file mode 100644 index 0000000000..800476fd03 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_privatebrowsing_protocolhandler_page.html @@ -0,0 +1,13 @@ + + + + Protocol registrar page + + + + + diff --git a/comm/suite/browser/test/browser/browser_relatedTabs.js b/comm/suite/browser/test/browser/browser_relatedTabs.js new file mode 100644 index 0000000000..f93e212391 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_relatedTabs.js @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + is(getBrowser().tabs.length, 1, "one tab is open initially"); + + // Add several new tabs in sequence, interrupted by selecting a + // different tab, moving a tab around and closing a tab, + // returning a list of opened tabs for verifying the expected order. + // The new tab behaviour is documented in bug 465673 + let tabs = []; + function addTab(aURL, aReferrer) { + tabs.push(getBrowser().addTab(aURL, {referrerURI: aReferrer})); + } + + addTab("http://mochi.test:8888/#0"); + getBrowser().selectedTab = tabs[0]; + addTab("http://mochi.test:8888/#1"); + addTab("http://mochi.test:8888/#2", getBrowser().currentURI); + addTab("http://mochi.test:8888/#3", getBrowser().currentURI); + getBrowser().selectedTab = tabs[tabs.length - 1]; + getBrowser().selectedTab = tabs[0]; + addTab("http://mochi.test:8888/#4", getBrowser().currentURI); + getBrowser().selectedTab = tabs[3]; + addTab("http://mochi.test:8888/#5", getBrowser().currentURI); + getBrowser().removeTab(tabs.pop()); + addTab("about:blank", getBrowser().currentURI); + getBrowser().moveTabTo(getBrowser().selectedTab, 1); + addTab("http://mochi.test:8888/#6", getBrowser().currentURI); + addTab(); + addTab("http://mochi.test:8888/#7"); + + function testPosition(tabNum, expectedPosition, msg) { + is(Array.from(getBrowser().tabs) + .indexOf(tabs[tabNum]), + expectedPosition, + msg); + } + + testPosition(0, 3, "tab without referrer was opened to the far right"); + testPosition(1, 7, "tab without referrer was opened to the far right"); + testPosition(2, 5, "tab with referrer opened immediately to the right"); + testPosition(3, 1, "next tab with referrer opened further to the right"); + testPosition(4, 4, "tab selection changed, tab opens immediately to the right"); + testPosition(5, 6, "blank tab with referrer opens to the right of 3rd original tab where removed tab was"); + testPosition(6, 2, "tab has moved, new tab opens immediately to the right"); + testPosition(7, 8, "blank tab without referrer opens at the end"); + testPosition(8, 9, "tab without referrer opens at the end"); + + tabs.forEach(getBrowser().removeTab, getBrowser()); +} diff --git a/comm/suite/browser/test/browser/browser_scope.js b/comm/suite/browser/test/browser/browser_scope.js new file mode 100644 index 0000000000..e4edac1e04 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_scope.js @@ -0,0 +1,4 @@ +function test() { + ok(!!gBrowser, "gBrowser exists"); + is(gBrowser, getBrowser(), "both ways of getting tabbrowser work"); +} diff --git a/comm/suite/browser/test/browser/browser_selectTabAtIndex.js b/comm/suite/browser/test/browser/browser_selectTabAtIndex.js new file mode 100644 index 0000000000..9da6c8d489 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_selectTabAtIndex.js @@ -0,0 +1,22 @@ +function test() { + for (let i = 0; i < 9; i++) + gBrowser.addTab(); + +/* This part tests accel keys, which are not implemented in Seamonkey yet. + * Commenting out for now ... + * var isLinux = AppConstants.platform == "linux"; + * for (let i = 9; i >= 1; i--) { + * EventUtils.synthesizeKey(i.toString(), { altKey: isLinux, accelKey: !isLinux }); + * + * is(gBrowser.tabContainer.selectedIndex, (i == 9 ? gBrowser.tabs.length : i) - 1, + * (isLinux ? "Alt" : "Accel") + "+" + i + " selects expected tab"); + * } + */ + + gBrowser.selectTabAtIndex(-3); + is(gBrowser.tabContainer.selectedIndex, gBrowser.tabs.length - 3, + "gBrowser.selectTabAtIndex(-3) selects expected tab"); + + for (let i = 0; i < 9; i++) + gBrowser.removeCurrentTab(); +} diff --git a/comm/suite/browser/test/browser/browser_urlbarCopying.js b/comm/suite/browser/test/browser/browser_urlbarCopying.js new file mode 100644 index 0000000000..cc51832996 --- /dev/null +++ b/comm/suite/browser/test/browser/browser_urlbarCopying.js @@ -0,0 +1,204 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const trimPref = "browser.urlbar.trimURLs"; +const phishyUserPassPref = "network.http.phishy-userpass-length"; + +function test() { + + let tab = gBrowser.selectedTab = gBrowser.addTab(); + + registerCleanupFunction(function () { + gBrowser.removeTab(tab); + Services.prefs.clearUserPref(trimPref); + Services.prefs.clearUserPref(phishyUserPassPref); + URLBarSetURI(); + }); + + Services.prefs.setBoolPref(trimPref, true); + Services.prefs.setIntPref(phishyUserPassPref, 32); // avoid prompting about phishing + + waitForExplicitFinish(); + + nextTest(); +} + +var tests = [ + // pageproxystate="invalid" + { + setURL: "http://example.com/", + expectedURL: "example.com", + copyExpected: "example.com" + }, + { + copyVal: "xample.com", + copyExpected: "e" + }, + + // pageproxystate="valid" from this point on (due to the load) + { + loadURL: "http://example.com/", + expectedURL: "example.com", + copyExpected: "http://example.com/" + }, + { + copyVal: "m", + copyExpected: "example.co" + }, + { + copyVal: "eample.com", + copyExpected: "x" + }, + { + copyVal: "xample.com", + copyExpected: "e" + }, + + { + loadURL: "http://example.com/foo", + expectedURL: "example.com/foo", + copyExpected: "http://example.com/foo" + }, + { + copyVal: "/foo", + copyExpected: "http://example.com" + }, + { + copyVal: ".com/foo", + copyExpected: "example" + }, + + // Test that userPass is stripped out + { + loadURL: "http://user:pass@mochi.test:8888/browser/browser/base/content/test/general/authenticate.sjs?user=user&pass=pass", + expectedURL: "mochi.test:8888/browser/browser/base/content/test/general/authenticate.sjs?user=user&pass=pass", + copyExpected: "http://mochi.test:8888/browser/browser/base/content/test/general/authenticate.sjs?user=user&pass=pass" + }, + + // Test escaping + { + loadURL: "http://example.com/()%28%29%C3%A9", + expectedURL: "example.com/()()\xe9", + copyExpected: "http://example.com/()%28%29%C3%A9" + }, + { + copyVal: ")()\xe9", + copyExpected: "http://example.com/(" + }, + { + copyVal: "e)()\xe9", + copyExpected: "xample.com/(" + }, + + { + loadURL: "http://example.com/%C3%A9%C3%A9", + expectedURL: "example.com/\xe9\xe9", + copyExpected: "http://example.com/%C3%A9%C3%A9" + }, + { + copyVal: "e\xe9", + copyExpected: "xample.com/\xe9" + }, + { + copyVal: "\xe9", + copyExpected: "http://example.com/\xe9" + }, + + { + loadURL: "http://example.com/?%C3%B7%C3%B7", + expectedURL: "example.com/?\xf7\xf7", + copyExpected: "http://example.com/?%C3%B7%C3%B7" + }, + { + copyVal: "e\xf7", + copyExpected: "xample.com/?\xf7" + }, + { + copyVal: "\xf7", + copyExpected: "http://example.com/?\xf7" + }, + + // data: and javsacript: URIs shouldn't be encoded + { + loadURL: "javascript:('%C3%A9%20%25%50')", + expectedURL: "javascript:('%C3%A9 %25P')", + copyExpected: "javascript:('%C3%A9 %25P')" + }, + { + copyVal: "'%C3%A9 %25P')", + copyExpected: "javascript:(" + }, + + { + loadURL: "data:text/html,(%C3%A9%20%25%50)", + expectedURL: "data:text/html,(%C3%A9 %25P)", + copyExpected: "data:text/html,(%C3%A9 %25P)", + }, + { + copyVal: "%C3%A9 %25P)", + copyExpected: "data:text/html,(" + }, + { + copyVal: ")", + copyExpected: "data:text/html,(%C3%A9 %25P", + } +]; + +function nextTest() { + let test = tests.shift(); + if (tests.length == 0) + runTest(test, finish); + else + runTest(test, nextTest); +} + +function runTest(test, cb) { + function doCheck() { + if (test.setURL || test.loadURL) { + gURLBar.valueIsTyped = !!test.setURL; + is(gURLBar.textValue, test.expectedURL, "url bar value set"); + } + + testCopy(test.copyVal, test.copyExpected, cb); + } + + if (test.loadURL) { + loadURL(test.loadURL, doCheck); + } else { + if (test.setURL) + gURLBar.value = test.setURL; + doCheck(); + } +} + +function testCopy(copyVal, targetValue, cb) { + info("Expecting copy of: " + targetValue); + waitForClipboard(targetValue, function () { + gURLBar.focus(); + if (copyVal) { + let startBracket = copyVal.indexOf("<"); + let endBracket = copyVal.indexOf(">"); + if (startBracket == -1 || endBracket == -1 || + startBracket > endBracket || + copyVal.replace("<", "").replace(">", "") != gURLBar.textValue) { + ok(false, "invalid copyVal: " + copyVal); + } + gURLBar.selectionStart = startBracket; + gURLBar.selectionEnd = endBracket - 1; + } else { + gURLBar.select(); + } + + goDoCommand("cmd_copy"); + }, cb, cb); +} + +function loadURL(aURL, aCB) { + gBrowser.selectedBrowser.addEventListener("load", function () { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + is(gBrowser.currentURI.spec, aURL, "loaded expected URL"); + aCB(); + }, true); + + gBrowser.loadURI(aURL); +} diff --git a/comm/suite/browser/test/browser/feed_tab.html b/comm/suite/browser/test/browser/feed_tab.html new file mode 100644 index 0000000000..50903f48b6 --- /dev/null +++ b/comm/suite/browser/test/browser/feed_tab.html @@ -0,0 +1,17 @@ + + + + + Test for page info feeds tab + + + + + + + + + + diff --git a/comm/suite/browser/test/browser/file_dom_notifications.html b/comm/suite/browser/test/browser/file_dom_notifications.html new file mode 100644 index 0000000000..078c94a42d --- /dev/null +++ b/comm/suite/browser/test/browser/file_dom_notifications.html @@ -0,0 +1,40 @@ + + + + + +
+ +
+ + diff --git a/comm/suite/browser/test/browser/head.js b/comm/suite/browser/test/browser/head.js new file mode 100644 index 0000000000..9a065930a2 --- /dev/null +++ b/comm/suite/browser/test/browser/head.js @@ -0,0 +1,63 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function findChromeWindowByURI(aURI) { + let windows = Services.wm.getEnumerator(null); + while (windows.hasMoreElements()) { + let win = windows.getNext(); + if (win.location.href == aURI) + return win; + } + return null; +} + +function waitForCondition(condition, nextTest, errorMsg) { + var tries = 0; + var interval = setInterval(function() { + if (tries >= 30) { + ok(false, errorMsg); + moveOn(); + } + if (condition()) { + moveOn(); + } + tries++; + }, 100); + var moveOn = function() { clearInterval(interval); nextTest(); }; +} + +function whenNewWindowLoaded(aOptions, aCallback) { + var { private } = aOptions; + var features = private ? "private,chrome,all,dialog=no" : + "non-private,chrome,all,dialog=no"; + var win = window.openDialog(getBrowserURL(), "_blank", features, + "about:blank"); + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad); + aCallback(win); + }); +} + +function updateBlocklist(aCallback) { + var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"] + .getService(Ci.nsITimerCallback); + var observer = function() { + Services.obs.removeObserver(observer, "blocklist-updated"); + SimpleTest.executeSoon(aCallback); + }; + Services.obs.addObserver(observer, "blocklist-updated"); + blocklistNotifier.notify(null); +} + +var _originalTestBlocklistURL = null; +function setAndUpdateBlocklist(aURL, aCallback) { + if (!_originalTestBlocklistURL) + _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url"); + Services.prefs.setCharPref("extensions.blocklist.url", aURL); + updateBlocklist(aCallback); +} + +function resetBlocklist() { + Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL); +} diff --git a/comm/suite/browser/test/browser/page_style_sample.html b/comm/suite/browser/test/browser/page_style_sample.html new file mode 100644 index 0000000000..633956c4c7 --- /dev/null +++ b/comm/suite/browser/test/browser/page_style_sample.html @@ -0,0 +1,31 @@ + + + Test for page style menu + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/comm/suite/browser/test/browser/redirect_bug623155.sjs b/comm/suite/browser/test/browser/redirect_bug623155.sjs new file mode 100644 index 0000000000..2e4681f240 --- /dev/null +++ b/comm/suite/browser/test/browser/redirect_bug623155.sjs @@ -0,0 +1,16 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host. + +function handleRequest(aRequest, aResponse) { + // Set HTTP Status + aResponse.setStatusLine(aRequest.httpVersion, 301, "Moved Permanently"); + + // Set redirect URI, mirroring the hash value. + let hash = (/\#.+/.test(aRequest.pathQueryRef))? + "#" + aRequest.pathQueryRef.split("#")[1]: + ""; + aResponse.setHeader("Location", REDIRECT_TO + hash); +} diff --git a/comm/suite/browser/test/browser/title_test.svg b/comm/suite/browser/test/browser/title_test.svg new file mode 100644 index 0000000000..6ab5b2f5c2 --- /dev/null +++ b/comm/suite/browser/test/browser/title_test.svg @@ -0,0 +1,59 @@ + + This is a root SVG element's title + + + + + This is a non-root SVG element title + + + + + + This contains only <title> + + + + This is a title + + + + + This contains only <desc> + This is a desc + + + This contains nothing. + + + This link contains <title> + + This is a title + + + + + + + This text contains <title> + + This is a title + + + + + + This link contains <title> & xlink:title attr. + This is a title + + + + + This link contains xlink:title attr. + + + + This contains nothing. + + diff --git a/comm/suite/browser/test/chrome/chrome.ini b/comm/suite/browser/test/chrome/chrome.ini new file mode 100644 index 0000000000..05c31c161a --- /dev/null +++ b/comm/suite/browser/test/chrome/chrome.ini @@ -0,0 +1,3 @@ +[DEFAULT] + +[test_maxSniffing.html] diff --git a/comm/suite/browser/test/chrome/test_maxSniffing.html b/comm/suite/browser/test/chrome/test_maxSniffing.html new file mode 100644 index 0000000000..8fb333e523 --- /dev/null +++ b/comm/suite/browser/test/chrome/test_maxSniffing.html @@ -0,0 +1,37 @@ + + + + + Test that we only sniff 512 bytes + + + + +Mozilla Bug 739040 +

+ + +

+ +
+
+
+ + diff --git a/comm/suite/browser/test/mochitest/audio.ogg b/comm/suite/browser/test/mochitest/audio.ogg new file mode 100644 index 0000000000..7e6ef77ec4 Binary files /dev/null and b/comm/suite/browser/test/mochitest/audio.ogg differ diff --git a/comm/suite/browser/test/mochitest/bug364677-data.xml b/comm/suite/browser/test/mochitest/bug364677-data.xml new file mode 100644 index 0000000000..b48915c050 --- /dev/null +++ b/comm/suite/browser/test/mochitest/bug364677-data.xml @@ -0,0 +1,5 @@ + + + t + + diff --git a/comm/suite/browser/test/mochitest/bug364677-data.xml^headers^ b/comm/suite/browser/test/mochitest/bug364677-data.xml^headers^ new file mode 100644 index 0000000000..f203c6368e --- /dev/null +++ b/comm/suite/browser/test/mochitest/bug364677-data.xml^headers^ @@ -0,0 +1 @@ +Content-Type: text/xml diff --git a/comm/suite/browser/test/mochitest/bug395533-data.txt b/comm/suite/browser/test/mochitest/bug395533-data.txt new file mode 100644 index 0000000000..e0ed39850f --- /dev/null +++ b/comm/suite/browser/test/mochitest/bug395533-data.txt @@ -0,0 +1,6 @@ + + + http://example.org/ + t + + diff --git a/comm/suite/browser/test/mochitest/bug436801-data.xml b/comm/suite/browser/test/mochitest/bug436801-data.xml new file mode 100644 index 0000000000..0e45c7ed8e --- /dev/null +++ b/comm/suite/browser/test/mochitest/bug436801-data.xml @@ -0,0 +1,44 @@ + + + + + <div xmlns="http://www.w3.org/1999/xhtml">Example of a <em>special</em> feed (<img height="20px" src="baz.png" alt="base test sprite"/>)</div> + + + + special subtitle (base test sprite) + ]]> + + + + + 2010-09-02T18:30:02Z + + + John Doe + + + urn:uuid:22906062-ecbd-46e2-b6a7-3039506a398f + + + + <div xmlns="http://www.w3.org/1999/xhtml">Some <abbr title="Extensible Hyper-text Mark-up Language">XHTML</abbr> examples (<img height="20px" src="baz.png" alt="base test sprite"/>)</div> + + urn:uuid:b48083a7-71a7-4c9c-8515-b7c0d22955e7 + 2010-09-02T18:30:02Z + Some text. + + + + + <![CDATA[ + Some <abbr title="Hyper-text Mark-up Language">HTML</abbr> examples (<img height="20px" src="baz.png" alt="base test sprite"/>) + ]]> + + urn:uuid:1424967a-280a-414d-b0ab-8b11c4ac1bb7 + 2010-09-02T18:30:02Z + Some text. + + + diff --git a/comm/suite/browser/test/mochitest/ctxmenu-image.png b/comm/suite/browser/test/mochitest/ctxmenu-image.png new file mode 100644 index 0000000000..4c3be50847 Binary files /dev/null and b/comm/suite/browser/test/mochitest/ctxmenu-image.png differ diff --git a/comm/suite/browser/test/mochitest/feed_discovery.html b/comm/suite/browser/test/mochitest/feed_discovery.html new file mode 100644 index 0000000000..80c35d19ab --- /dev/null +++ b/comm/suite/browser/test/mochitest/feed_discovery.html @@ -0,0 +1,112 @@ + + + + + Test for feed discovery + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/comm/suite/browser/test/mochitest/mochitest.ini b/comm/suite/browser/test/mochitest/mochitest.ini new file mode 100644 index 0000000000..99b25bba9d --- /dev/null +++ b/comm/suite/browser/test/mochitest/mochitest.ini @@ -0,0 +1,17 @@ +[DEFAULT] +support-files = + valid-feed.xml + valid-unsniffable-feed.xml + +[test_bug364677.html] +support-files = bug364677-data.xml bug364677-data.xml^headers^ +[test_bug395533.html] +support-files = bug395533-data.txt +[test_bug436801.html] +support-files = bug436801-data.xml +[test_contextmenu.html] +support-files = audio.ogg ctxmenu-image.png subtst_contextmenu.html video.ogg +skip-if = os != "win" # disabled on Linux due to bug 513558, on Mac after 10.6 due to bug 792304 +[test_feed_discovery.html] +support-files = feed_discovery.html +[test_registerHandler.html] diff --git a/comm/suite/browser/test/mochitest/subtst_contextmenu.html b/comm/suite/browser/test/mochitest/subtst_contextmenu.html new file mode 100644 index 0000000000..63cc70c84e --- /dev/null +++ b/comm/suite/browser/test/mochitest/subtst_contextmenu.html @@ -0,0 +1,70 @@ + + + + Subtest for browser context menu + + +Browser context menu subtest. + +
Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+Click the monkey! +Mail the monkey!
+
+
+ + + + + + + + + + + +
chssseefsbbbie
+ +
+

I've got a context menu!

+ + + + Item w/ textContent + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +Click the monkey! +Mail the monkey!
+ + + diff --git a/comm/suite/browser/test/mochitest/test_bug364677.html b/comm/suite/browser/test/mochitest/test_bug364677.html new file mode 100644 index 0000000000..e08b1403f6 --- /dev/null +++ b/comm/suite/browser/test/mochitest/test_bug364677.html @@ -0,0 +1,32 @@ + + + + + Test for Bug 364677 + + + + +Mozilla Bug 364677 +

+ +
+
+
+ + + diff --git a/comm/suite/browser/test/mochitest/test_bug395533.html b/comm/suite/browser/test/mochitest/test_bug395533.html new file mode 100644 index 0000000000..5d3cfa0121 --- /dev/null +++ b/comm/suite/browser/test/mochitest/test_bug395533.html @@ -0,0 +1,39 @@ + + + + + Test for Bug 395533 + + + + +Mozilla Bug 395533 +

+ +
+
+
+ + + diff --git a/comm/suite/browser/test/mochitest/test_bug436801.html b/comm/suite/browser/test/mochitest/test_bug436801.html new file mode 100644 index 0000000000..6ca1b83cf5 --- /dev/null +++ b/comm/suite/browser/test/mochitest/test_bug436801.html @@ -0,0 +1,118 @@ + + + + + Test feed preview subscribe UI + + + + + +Mozilla Bug 436801 +

+ +
+
+
+ + diff --git a/comm/suite/browser/test/mochitest/test_contextmenu.html b/comm/suite/browser/test/mochitest/test_contextmenu.html new file mode 100644 index 0000000000..0dea49c925 --- /dev/null +++ b/comm/suite/browser/test/mochitest/test_contextmenu.html @@ -0,0 +1,931 @@ + + + + Tests for browser context menu + + + + + +Browser context menu tests. +

+ +
+
+ +
+
+
+ + diff --git a/comm/suite/browser/test/mochitest/test_feed_discovery.html b/comm/suite/browser/test/mochitest/test_feed_discovery.html new file mode 100644 index 0000000000..2f2a0a459e --- /dev/null +++ b/comm/suite/browser/test/mochitest/test_feed_discovery.html @@ -0,0 +1,56 @@ + + + + + Test for feed discovery + + + + +Mozilla Bug 377611 +

+ +
+
+
+ + diff --git a/comm/suite/browser/test/mochitest/test_registerHandler.html b/comm/suite/browser/test/mochitest/test_registerHandler.html new file mode 100644 index 0000000000..20952ca9ee --- /dev/null +++ b/comm/suite/browser/test/mochitest/test_registerHandler.html @@ -0,0 +1,85 @@ + + + + + Test for Bug 402788 + + + + +Mozilla Bug 402788 +

+ +
+
+
+ + diff --git a/comm/suite/browser/test/mochitest/valid-feed.xml b/comm/suite/browser/test/mochitest/valid-feed.xml new file mode 100644 index 0000000000..0e700b6d8d --- /dev/null +++ b/comm/suite/browser/test/mochitest/valid-feed.xml @@ -0,0 +1,23 @@ + + + + Example Feed + + 2010-08-22T18:30:02Z + + + John Doe + + urn:uuid:e2df8375-99be-4848-b05e-b9d407555267 + + + + Item + + urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a + 2010-08-22T18:30:02Z + + Some text. + + + diff --git a/comm/suite/browser/test/mochitest/valid-unsniffable-feed.xml b/comm/suite/browser/test/mochitest/valid-unsniffable-feed.xml new file mode 100644 index 0000000000..e753157395 --- /dev/null +++ b/comm/suite/browser/test/mochitest/valid-unsniffable-feed.xml @@ -0,0 +1,32 @@ + + + + + Example Feed + + 2010-08-22T18:30:02Z + + + John Doe + + urn:uuid:e2df8375-99be-4848-b05e-b9d407555267 + + + + Item + + urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a + 2010-08-22T18:30:02Z + + Some text. + + + diff --git a/comm/suite/browser/test/mochitest/video.ogg b/comm/suite/browser/test/mochitest/video.ogg new file mode 100644 index 0000000000..ac7ece3519 Binary files /dev/null and b/comm/suite/browser/test/mochitest/video.ogg differ diff --git a/comm/suite/browser/urlbarBindings.xml b/comm/suite/browser/urlbarBindings.xml new file mode 100644 index 0000000000..2026724e5a --- /dev/null +++ b/comm/suite/browser/urlbarBindings.xml @@ -0,0 +1,694 @@ + + + + + + %textcontextDTD; + + %navigatorDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + true + + + + + + + + + + + + + + + + escape(c)); + } + } + + return val; + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + document.getAnonymousElementByAttribute(this, "role", "search-box"); + + + + + + + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + this.view.selection.clearSelection(); + this.mSearchBox.selectedIndex = -1; + + + + + + + + + + + + + + + + + + + + + + + + + -1 + + + + + + return this.mSelectedIndex; + + + + + + + + + + + + + + + + + + + + + + + + + this.parentNode.textbox.onResultClick(); + + + + + + + + + + + + + + this.parentNode.selectedIndex = Number(this.getAttribute("engineIndex")); + + + + this.parentNode.selectedIndex = -1; + + + + + + + + + + + + + + + + + + + + + + diff --git a/comm/suite/browser/webDeveloperOverlay.js b/comm/suite/browser/webDeveloperOverlay.js new file mode 100644 index 0000000000..41277681f6 --- /dev/null +++ b/comm/suite/browser/webDeveloperOverlay.js @@ -0,0 +1,197 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +ChromeUtils.defineModuleGetter(this, "BrowserToolboxProcess", + "resource://devtools/client/framework/ToolboxProcess.jsm"); + +XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() { + var tmp = {}; + ChromeUtils.import("resource://devtools/shared/Loader.jsm", tmp); + var DeveloperToolbar = tmp.require("devtools/client/shared/developer-toolbar").DeveloperToolbar; + return new DeveloperToolbar(window); +}); + +var ResponsiveUI = { + toggle() { + this.ResponsiveUIManager.toggle(window, getBrowser().selectedTab); + } +}; + +XPCOMUtils.defineLazyGetter(ResponsiveUI, "ResponsiveUIManager", function() { + var tmp = {}; + ChromeUtils.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", tmp); + return tmp.ResponsiveUIManager; +}); + +var Scratchpad = { + openScratchpad() { + return this.ScratchpadManager.openScratchpad(); + } +}; + +XPCOMUtils.defineLazyGetter(Scratchpad, "ScratchpadManager", function() { + var tmp = {}; + ChromeUtils.import("resource://devtools/client/scratchpad/scratchpad-manager.jsm", tmp); + return tmp.ScratchpadManager; +}); + +ChromeUtils.defineModuleGetter(this, "gDevTools", + "resource://devtools/client/framework/gDevTools.jsm"); + +ChromeUtils.defineModuleGetter(this, "gDevToolsBrowser", + "resource://devtools/client/framework/gDevTools.jsm"); + +function openEyedropper() { + var eyedropper = new this.Eyedropper(this, { context: "menu", + copyOnSelect: true }); + eyedropper.open(); +} + +Object.defineProperty(this, "Eyedropper", { + get() { + var tmp = {}; + ChromeUtils.import("resource://devtools/shared/Loader.jsm", tmp); + return tmp.require("devtools/client/eyedropper/eyedropper").Eyedropper; + }, + configurable: true, + enumerable: true +}); + +Object.defineProperty(this, "HUDService", { + get() { + var tmp = {}; + ChromeUtils.import("resource://devtools/shared/Loader.jsm", tmp); + return tmp.require("devtools/client/webconsole/hudservice").HUDService; + }, + configurable: true, + enumerable: true +}); + +var gWebDeveloper = { + validateThisPage: function() { + var service = GetLocalizedStringPref("browser.validate.html.service"); + var uri = getBrowser().currentURI; + var checkURL = service + encodeURIComponent(uri.spec); + var opentab = Services.prefs.getBoolPref("browser.tabs.opentabfor.middleclick"); + openUILinkIn(checkURL, opentab ? "tabfocused" : "window", + { referrerURI: uri, relatedToCurrent: true }); + }, + + enableDebugger: function(aItem) { + var shouldEnable = aItem.getAttribute("checked") == "true"; + Services.prefs.setBoolPref("devtools.debugger.remote-enabled", shouldEnable); + }, + + handleEvent: function(aEvent) { + switch (aEvent.type) { + case "load": + window.removeEventListener("load", gWebDeveloper); + window.addEventListener("unload", gWebDeveloper); + var popup = document.getElementById("toolsPopup"); + popup.addEventListener("popupshowing", gWebDeveloper); + // Don't use gDevToolsBrowser.updateCommandAvailability() at the moment + // because some tools aren't working. + if (!gDevToolsBrowser._old_updateCommandAvailability) { + gDevToolsBrowser._old_updateCommandAvailability = gDevToolsBrowser.updateCommandAvailability; + gDevToolsBrowser.updateCommandAvailability = this.updateCommandAvailability; + } + // Add Devtools menuitems, observers, and listeners + gDevToolsBrowser.registerBrowserWindow(window); + Services.prefs.addObserver(this.devtoolsThemePref, this); + this.updateDevtoolsThemeAttribute(); + break; + + case "unload": + window.removeEventListener("unload", gWebDeveloper); + gDevToolsBrowser.forgetBrowserWindow(window); + Services.prefs.removeObserver(this.devtoolsThemePref, this); + + var desc = Object.getOwnPropertyDescriptor(window, "DeveloperToolbar"); + if (desc && !desc.get) + DeveloperToolbar.destroy(); + break; + + case "popupshowing": + this.initMenuItems(); + this.updateCommandAvailability(window); + break; + } + }, + + initMenuItems: function() { + var menuitem = document.getElementById("validatePage"); + var uri = getBrowser().currentURI; + if (uri && (uri.schemeIs("http") || uri.schemeIs("https"))) + menuitem.removeAttribute("disabled"); + else + menuitem.setAttribute("disabled", true); + + var enabled = Services.prefs + .getBoolPref("devtools.debugger.remote-enabled"); + document.getElementById("devtoolsDebugger") + .setAttribute("checked", enabled); + }, + + devtoolsThemePref: "devtools.theme", + + observe: function (subject, topic, data) { + if (topic == "nsPref:changed" && data == this.devtoolsThemePref) { + this.updateDevtoolsThemeAttribute(); + } + }, + + updateDevtoolsThemeAttribute: function() { + // Set an attribute on root element to make it possible + // to change colors based on the selected devtools theme. + var devtoolsTheme = Services.prefs.getCharPref(this.devtoolsThemePref); + // Bug 1096469 - Make devedition theme work with custom devtools themes. + if (devtoolsTheme != "dark") + devtoolsTheme = "light"; + + document.documentElement.setAttribute("devtoolstheme", devtoolsTheme); + // document.getElementById("developer-toolbar").setAttribute("devtoolstheme", devtoolsTheme); + }, + + updateCommandAvailability: function(win) { + var doc = win.document; + + function toggleCmd(id, isEnabled) { + var cmd = doc.getElementById(id); + if (isEnabled) { + cmd.removeAttribute("disabled"); + cmd.removeAttribute("hidden"); + } else { + cmd.setAttribute("disabled", "true"); + cmd.setAttribute("hidden", "true"); + } + }; + + // Enable developer toolbar? + var devToolbarEnabled = Services.prefs.getBoolPref("devtools.toolbar.enabled"); + toggleCmd("Tools:DevToolbar", devToolbarEnabled); + var focusEl = doc.getElementById("Tools:DevToolbarFocus"); + if (devToolbarEnabled) + focusEl.removeAttribute("disabled"); + else + focusEl.setAttribute("disabled", "true"); + + if (devToolbarEnabled && Services.prefs.getBoolPref("devtools.toolbar.visible")) + win.DeveloperToolbar.show(false).catch(console.error); + + // Enable Browser Toolbox? + var chromeEnabled = Services.prefs.getBoolPref("devtools.chrome.enabled"); + var devtoolsRemoteEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled"); + var remoteEnabled = chromeEnabled && devtoolsRemoteEnabled; + toggleCmd("Tools:BrowserToolbox", remoteEnabled); + // Currently "gMultiProcessBrowser" is always falsey. + //toggleCmd("Tools:BrowserContentToolbox", remoteEnabled && win.gMultiProcessBrowser); + toggleCmd("Tools:BrowserContentToolbox", false); + + // Enable DevTools connection screen, if the preference allows this. + toggleCmd("Tools:DevToolsConnect", devtoolsRemoteEnabled); + }, +} + +window.addEventListener("load", gWebDeveloper); diff --git a/comm/suite/browser/webDeveloperOverlay.xul b/comm/suite/browser/webDeveloperOverlay.xul new file mode 100644 index 0000000000..09780ee6f0 --- /dev/null +++ b/comm/suite/browser/webDeveloperOverlay.xul @@ -0,0 +1,156 @@ + + + + + + + + + + +