summaryrefslogtreecommitdiffstats
path: root/comm/suite/browser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/browser
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/suite/browser')
-rw-r--r--comm/suite/browser/SuiteBrowser.manifest23
-rw-r--r--comm/suite/browser/browser-places.js983
-rw-r--r--comm/suite/browser/content.js901
-rw-r--r--comm/suite/browser/fullScreen.js107
-rw-r--r--comm/suite/browser/hiddenWindow.xul55
-rw-r--r--comm/suite/browser/jar.mn41
-rw-r--r--comm/suite/browser/linkToolbarHandler.js295
-rw-r--r--comm/suite/browser/linkToolbarItem.js215
-rw-r--r--comm/suite/browser/linkToolbarOverlay.js213
-rw-r--r--comm/suite/browser/linkToolbarOverlay.xul165
-rw-r--r--comm/suite/browser/mailNavigatorOverlay.js176
-rw-r--r--comm/suite/browser/mailNavigatorOverlay.xul96
-rw-r--r--comm/suite/browser/metadata.js526
-rw-r--r--comm/suite/browser/metadata.xul192
-rw-r--r--comm/suite/browser/moz.build22
-rw-r--r--comm/suite/browser/navigator.css135
-rw-r--r--comm/suite/browser/navigator.js3311
-rw-r--r--comm/suite/browser/navigator.xul571
-rw-r--r--comm/suite/browser/navigatorDD.js121
-rw-r--r--comm/suite/browser/navigatorOverlay.xul716
-rw-r--r--comm/suite/browser/nsBrowserContentHandler.js634
-rw-r--r--comm/suite/browser/nsBrowserContentListener.js138
-rw-r--r--comm/suite/browser/nsBrowserStatusHandler.js473
-rw-r--r--comm/suite/browser/nsTypeAheadFind.js416
-rw-r--r--comm/suite/browser/pageinfo/feeds.js31
-rw-r--r--comm/suite/browser/pageinfo/feeds.xml40
-rw-r--r--comm/suite/browser/pageinfo/pageInfo.css18
-rw-r--r--comm/suite/browser/pageinfo/pageInfo.js1177
-rw-r--r--comm/suite/browser/pageinfo/pageInfo.xul531
-rw-r--r--comm/suite/browser/pageinfo/permissions.js204
-rw-r--r--comm/suite/browser/pageinfo/security.js354
-rw-r--r--comm/suite/browser/safeBrowsingOverlay.js75
-rw-r--r--comm/suite/browser/safeBrowsingOverlay.xul32
-rw-r--r--comm/suite/browser/sessionHistoryUI.js172
-rw-r--r--comm/suite/browser/tabbrowser.xml3707
-rw-r--r--comm/suite/browser/test/browser/alltabslistener.html8
-rw-r--r--comm/suite/browser/test/browser/authenticate.sjs210
-rw-r--r--comm/suite/browser/test/browser/browser.ini38
-rw-r--r--comm/suite/browser/test/browser/browser_alltabslistener.js201
-rw-r--r--comm/suite/browser/test/browser/browser_bug329212.js42
-rw-r--r--comm/suite/browser/test/browser/browser_bug409624.js57
-rw-r--r--comm/suite/browser/test/browser/browser_bug413915.js70
-rw-r--r--comm/suite/browser/test/browser/browser_bug427559.js39
-rw-r--r--comm/suite/browser/test/browser/browser_bug435325.js56
-rw-r--r--comm/suite/browser/test/browser/browser_bug462289.js87
-rw-r--r--comm/suite/browser/test/browser/browser_bug519216.js50
-rw-r--r--comm/suite/browser/test/browser/browser_bug561636.js459
-rw-r--r--comm/suite/browser/test/browser/browser_bug562649.js26
-rw-r--r--comm/suite/browser/test/browser/browser_bug581947.js95
-rw-r--r--comm/suite/browser/test/browser/browser_bug585511.js24
-rw-r--r--comm/suite/browser/test/browser/browser_bug595507.js39
-rw-r--r--comm/suite/browser/test/browser/browser_bug623155.js136
-rw-r--r--comm/suite/browser/test/browser/browser_ctrlTab.js197
-rw-r--r--comm/suite/browser/test/browser/browser_fayt.js25
-rw-r--r--comm/suite/browser/test/browser/browser_notification_tab_switching.js95
-rw-r--r--comm/suite/browser/test/browser/browser_pageInfo.js38
-rw-r--r--comm/suite/browser/test/browser/browser_page_style_menu.js67
-rw-r--r--comm/suite/browser/test/browser/browser_popupNotification.js782
-rw-r--r--comm/suite/browser/test/browser/browser_privatebrowsing_protocolhandler.js65
-rw-r--r--comm/suite/browser/test/browser/browser_privatebrowsing_protocolhandler_page.html13
-rw-r--r--comm/suite/browser/test/browser/browser_relatedTabs.js52
-rw-r--r--comm/suite/browser/test/browser/browser_scope.js4
-rw-r--r--comm/suite/browser/test/browser/browser_selectTabAtIndex.js22
-rw-r--r--comm/suite/browser/test/browser/browser_urlbarCopying.js204
-rw-r--r--comm/suite/browser/test/browser/feed_tab.html17
-rw-r--r--comm/suite/browser/test/browser/file_dom_notifications.html40
-rw-r--r--comm/suite/browser/test/browser/head.js63
-rw-r--r--comm/suite/browser/test/browser/page_style_sample.html31
-rw-r--r--comm/suite/browser/test/browser/redirect_bug623155.sjs16
-rw-r--r--comm/suite/browser/test/browser/title_test.svg59
-rw-r--r--comm/suite/browser/test/chrome/chrome.ini3
-rw-r--r--comm/suite/browser/test/chrome/test_maxSniffing.html37
-rw-r--r--comm/suite/browser/test/mochitest/audio.oggbin0 -> 47411 bytes
-rw-r--r--comm/suite/browser/test/mochitest/bug364677-data.xml5
-rw-r--r--comm/suite/browser/test/mochitest/bug364677-data.xml^headers^1
-rw-r--r--comm/suite/browser/test/mochitest/bug395533-data.txt6
-rw-r--r--comm/suite/browser/test/mochitest/bug436801-data.xml44
-rw-r--r--comm/suite/browser/test/mochitest/ctxmenu-image.pngbin0 -> 5401 bytes
-rw-r--r--comm/suite/browser/test/mochitest/feed_discovery.html112
-rw-r--r--comm/suite/browser/test/mochitest/mochitest.ini17
-rw-r--r--comm/suite/browser/test/mochitest/subtst_contextmenu.html70
-rw-r--r--comm/suite/browser/test/mochitest/test_bug364677.html32
-rw-r--r--comm/suite/browser/test/mochitest/test_bug395533.html39
-rw-r--r--comm/suite/browser/test/mochitest/test_bug436801.html118
-rw-r--r--comm/suite/browser/test/mochitest/test_contextmenu.html931
-rw-r--r--comm/suite/browser/test/mochitest/test_feed_discovery.html56
-rw-r--r--comm/suite/browser/test/mochitest/test_registerHandler.html85
-rw-r--r--comm/suite/browser/test/mochitest/valid-feed.xml23
-rw-r--r--comm/suite/browser/test/mochitest/valid-unsniffable-feed.xml32
-rw-r--r--comm/suite/browser/test/mochitest/video.oggbin0 -> 285310 bytes
-rw-r--r--comm/suite/browser/urlbarBindings.xml694
-rw-r--r--comm/suite/browser/webDeveloperOverlay.js197
-rw-r--r--comm/suite/browser/webDeveloperOverlay.xul156
93 files changed, 22951 insertions, 0 deletions
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 <browser> 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 <menu> 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 <menu> 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 <menu> 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 <menu> 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, <input type="image"> 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 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xul-overlay href="chrome://navigator/content/navigatorOverlay.xul"?>
+
+<!-- hiddenwindow is a "minimal" XUL window intended for creating the,
+ er, hidden window. This window is never shown, but on platforms
+ which leave the app running after the last (visible) window is shut
+ down, this window does hold a browser menubar.
+ Though this window looks a lot like navigator.xul, that xul
+ is unsuitable because it's subject to the whims of its associated
+ appcore, which among other things causes it to load content documents
+ undesirable for this window.
+ Arguably a simpler menu structure could be substituted, but
+ the full one was included for now in anticipation of the whole thing
+ becoming an included file someday. -->
+
+<!-- Localizable string definitions from navigator.xul. -->
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % navigatorDTD SYSTEM "chrome://navigator/locale/navigator.dtd" >
+%navigatorDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ title="hidden"
+ titlemodifier="&mainWindow.titlemodifier;"
+ titlemenuseparator="&mainWindow.titlemodifiermenuseparator;"
+ onload="hiddenWindowStartup();"
+ onunload="Shutdown();">
+
+ <stringbundleset id="stringbundleset"/>
+
+ <!-- keys are appended from the overlay -->
+ <keyset id="navKeys"/>
+
+ <!-- commands are appended from the overlay -->
+ <commandset id="commands"/>
+
+ <broadcasterset id="navBroadcasters"/>
+
+ <!-- it's the whole navigator.xul menubar! hidden windows need to
+ have a menubar for situations where they're the only window remaining
+ on a platform that wants to leave the app running, like the Mac.
+ -->
+ <toolbox id="toolbox">
+ <menubar id="main-menubar" position="1"/>
+ </toolbox>
+
+</window>
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 <link> 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 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://navigator/skin/linkToolbar.css" type="text/css"?>
+<!DOCTYPE overlay SYSTEM "chrome://navigator/locale/linkToolbar.dtd">
+
+<overlay id="linkToolbarOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:rdf="http://www.mozilla.org/rdf">
+
+ <!-- classes -->
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://navigator/content/linkToolbarHandler.js" />
+ <script src="chrome://navigator/content/linkToolbarItem.js" />
+
+ <!-- functions -->
+ <script src="chrome://navigator/content/linkToolbarOverlay.js" />
+
+ <script>
+ <![CDATA[
+ document.addEventListener("pageshow", linkToolbarUI.initHandlers, true);
+ ]]>
+ </script>
+
+ <stringbundleset>
+ <stringbundle id="languageBundle"
+ src="chrome://global/locale/languageNames.properties"/>
+ <stringbundle id="regionBundle"
+ src="chrome://global/locale/regionNames.properties"/>
+ </stringbundleset>
+
+ <menupopup id="view_toolbars_popup">
+ <menu label="&linkToolbar.label;"
+ accesskey="&linkToolbar.accesskey;"
+ insertbefore="menuitem_showhide_tabbar"
+ onpopupshowing="linkToolbarUI.initLinkbarVisibilityMenu();"
+ oncommand="linkToolbarUI.toggleLinkToolbar(event.target);">
+ <menupopup>
+ <menuitem label="&linkToolbarAlways.label;"
+ accesskey="&linkToolbarAlways.accesskey;"
+ class="menuitem-iconic" type="radio" value="false"
+ name="cmd_viewlinktoolbar" id="cmd_viewlinktoolbar_false" />
+ <menuitem label="&linkToolbarAsNeeded.label;"
+ accesskey="&linkToolbarAsNeeded.accesskey;"
+ class="menuitem-iconic" type="radio" value="maybe"
+ name="cmd_viewlinktoolbar" id="cmd_viewlinktoolbar_maybe" />
+ <menuitem label="&linkToolbarNever.label;"
+ accesskey="&linkToolbarNever.accesskey;"
+ class="menuitem-iconic" type="radio" value="true"
+ name="cmd_viewlinktoolbar" id="cmd_viewlinktoolbar_true" />
+ </menupopup>
+ </menu>
+ </menupopup>
+
+ <toolbox id="navigator-toolbox">
+ <toolbar id="linktoolbar"
+ grippytooltiptext="&linkToolbar.tooltip;"
+ class="chromeclass-directories"
+ hidden="true"
+ hasitems="false"
+ nowindowdrag="true"
+ oncommand="event.stopPropagation(); return linkToolbarUI.commanded(event);">
+
+ <toolbarbutton id="link-top" class="bookmark-item"
+ label="&topButton.label;" disabled="true"/>
+
+ <toolbarbutton id="link-up" class="bookmark-item"
+ label="&upButton.label;" disabled="true"/>
+
+ <toolbarseparator />
+
+ <toolbarbutton id="link-first" class="bookmark-item"
+ label="&firstButton.label;" disabled="true"/>
+
+ <toolbarbutton id="link-prev" class="bookmark-item"
+ label="&prevButton.label;" disabled="true"/>
+
+ <toolbarbutton id="link-next" class="bookmark-item"
+ label="&nextButton.label;" disabled="true"/>
+
+ <toolbarbutton id="link-last" class="bookmark-item"
+ label="&lastButton.label;" disabled="true"/>
+
+ <toolbarseparator />
+
+ <toolbarbutton id="document-menu" class="bookmark-item"
+ type="menu"
+ container="true"
+ label="&documentButton.label;" disabled="true">
+ <menupopup id="document-menu-popup">
+
+ <menuitem id="link-toc" label="&tocButton.label;" disabled="true"
+ class="menuitem-iconic bookmark-item"/>
+ <menu id="link-chapter" label="&chapterButton.label;" disabled="true"
+ class="menu-iconic bookmark-item" container="true">
+ <menupopup id="link-chapter-popup" />
+ </menu>
+ <menu id="link-section" label="&sectionButton.label;" disabled="true"
+ class="menu-iconic bookmark-item" container="true">
+ <menupopup id="link-section-popup" />
+ </menu>
+ <menu id="link-subsection" label="&subSectionButton.label;" disabled="true"
+ class="menu-iconic bookmark-item" container="true">
+ <menupopup id="link-subsection-popup" />
+ </menu>
+ <menu id="link-appendix" label="&appendixButton.label;" disabled="true"
+ class="menu-iconic bookmark-item" container="true">
+ <menupopup id="link-appendix-popup" />
+ </menu>
+ <menuitem id="link-glossary" label="&glossaryButton.label;" disabled="true"
+ class="menuitem-iconic bookmark-item"/>
+ <menuitem id="link-index" label="&indexButton.label;" disabled="true"
+ class="menuitem-iconic bookmark-item"/>
+
+ </menupopup>
+ </toolbarbutton>
+
+ <toolbarbutton id="more-menu" class="bookmark-item"
+ type="menu"
+ container="true"
+ label="&moreButton.label;" disabled="true">
+ <menupopup id="more-menu-popup">
+
+ <menuitem id="link-help" label="&helpButton.label;" disabled="true"
+ class="menuitem-iconic bookmark-item"/>
+ <menuitem id="link-search" label="&searchButton.label;" disabled="true"
+ class="menuitem-iconic bookmark-item"/>
+
+ <menuseparator />
+
+ <menuitem id="link-author" label="&authorButton.label;" disabled="true"
+ class="menuitem-iconic bookmark-item"/>
+ <menuitem id="link-copyright" label="&copyrightButton.label;" disabled="true"
+ class="menuitem-iconic bookmark-item"/>
+
+ <menuseparator />
+
+ <menu id="link-bookmark" label="&bookmarkButton.label;" disabled="true"
+ class="menu-iconic bookmark-item" container="true">
+ <menupopup id="link-bookmark-popup" />
+ </menu>
+
+ <menuseparator />
+
+ <menu id="link-alternate" label="&alternateButton.label;" disabled="true"
+ class="menu-iconic bookmark-item" container="true">
+ <menupopup id="link-alternate-popup" />
+ </menu>
+
+ <menuseparator collapsed="true" id="misc-separator" />
+
+ </menupopup>
+ </toolbarbutton>
+
+ <toolbarbutton id="link-feed" class="bookmark-item"
+ type="menu"
+ container="true"
+ label="&feedButton.label;" disabled="true">
+ <menupopup id="link-feed-popup"/>
+ </toolbarbutton>
+ </toolbar>
+ </toolbox>
+</overlay>
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 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://navigator/locale/mailNavigatorOverlay.dtd" >
+
+<overlay id="mailNavigatorOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://navigator/content/mailNavigatorOverlay.js"/>
+
+ <!-- navigator specific commands -->
+ <commandset id="tasksCommands">
+ <command id="cmd_newMessage" oncommand="goOpenNewMessage();"/>
+ <command id="cmd_newCard" oncommand="openNewCardDialog()"/>
+ <command id="cmd_sendPage" oncommand="sendPage();"/>
+ <command id="Browser:SendLink" oncommand="sendLink();"/>
+ </commandset>
+ <keyset id="tasksKeys">
+#ifdef XP_MACOSX
+ <key id="key_newMessage" key="&newMessageCmd.key;"
+ modifiers="accel,shift" command="cmd_newMessage"/>
+#else
+ <key id="key_newMessage" key="&newMessageCmd.key;"
+ modifiers="accel" command="cmd_newMessage"/>
+#endif
+ </keyset>
+
+ <!-- navigator specific UI items -->
+ <menupopup id="menu_NewPopup">
+ <menuitem id="menu_newCard"
+ label="&newContactCmd.label;"
+ accesskey="&newContactCmd.accesskey;"
+ command="cmd_newCard"
+ insertafter="navBeginGlobalNewItems"/>
+ <menuitem id="menu_newMessage"
+ label="&newMessageCmd.label;"
+ accesskey="&newMessageCmd.accesskey;"
+ command="cmd_newMessage"
+ key="key_newMessage"
+ insertafter="navBeginGlobalNewItems"/>
+ </menupopup>
+
+ <menupopup id="menu_FilePopup">
+ <menuitem id="menu_sendPage"
+ label="&sendPage.label;"
+ accesskey="&sendPage.accesskey;"
+ command="cmd_sendPage"
+ position="9"/>
+ <menuitem id="menu_sendLink"
+ label="&sendLinkCmd.label;"
+ accesskey="&sendLinkCmd.accesskey;"
+ command="Browser:SendLink"
+ position="10"/>
+ </menupopup>
+
+ <menupopup id="contentAreaContextMenu">
+ <menuitem id="context-sendpage"
+ label="&contextSendThisPage.label;"
+ accesskey="&contextSendThisPage.accesskey;"
+ oncommand="sendPage();"
+ insertafter="context-savepage"/>
+ <menuitem id="context-sendimage"
+ label="&contextSendImage.label;"
+ accesskey="&contextSendImage.accesskey;"
+ oncommand="sendMedia(gContextMenu.mediaURL);"
+ insertafter="context-saveimage"/>
+ <menuitem id="context-sendvideo"
+ label="&contextSendVideo.label;"
+ accesskey="&contextSendVideo.accesskey;"
+ oncommand="sendMedia(gContextMenu.mediaURL);"
+ insertafter="context-savevideo"/>
+ <menuitem id="context-sendaudio"
+ label="&contextSendAudio.label;"
+ accesskey="&contextSendAudio.accesskey;"
+ oncommand="sendMedia(gContextMenu.mediaURL);"
+ insertafter="context-saveaudio"/>
+ <menuitem id="context-sendlink"
+ label="&contextSendThisLink.label;"
+ accesskey="&contextSendThisLink.accesskey;"
+ oncommand="sendLink(gContextMenu.linkURL);"
+ insertafter="context-savelink"/>
+ <menu id="frame">
+ <menupopup id="frame_popup">
+ <menuitem id="context-sendframe"
+ insertafter="context-saveframe"
+ label="&contextSendFrame.label;"
+ accesskey="&contextSendFrame.accesskey;"
+ oncommand="sendPage(gContextMenu.target.ownerDocument);"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+
+</overlay>
+
diff --git a/comm/suite/browser/metadata.js b/comm/suite/browser/metadata.js
new file mode 100644
index 0000000000..604ef54bb5
--- /dev/null
+++ b/comm/suite/browser/metadata.js
@@ -0,0 +1,526 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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/. */
+
+const MathMLNS = "http://www.w3.org/1998/Math/MathML";
+const XLinkNS = "http://www.w3.org/1999/xlink";
+const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const XMLNS = "http://www.w3.org/XML/1998/namespace";
+const XHTMLNS = "http://www.w3.org/1999/xhtml";
+var gMetadataBundle;
+var gLangBundle;
+var gRegionBundle;
+var nodeView;
+var htmlMode = false;
+
+var onLink = false;
+var onImage = false;
+var onInsDel = false;
+var onQuote = false;
+var onMisc = false;
+var onTable = false;
+var onTitle = false;
+var onLang = false;
+
+function onLoad()
+{
+ gMetadataBundle = document.getElementById("bundle_metadata");
+ gLangBundle = document.getElementById("bundle_languages");
+ gRegionBundle = document.getElementById("bundle_regions");
+
+ showMetadataFor(window.arguments[0]);
+
+ nodeView = window.arguments[0].ownerDocument.defaultView;
+}
+
+function showMetadataFor(elem)
+{
+ // skip past non-element nodes
+ while (elem && elem.nodeType != Node.ELEMENT_NODE)
+ elem = elem.parentNode;
+
+ if (!elem) {
+ alert(gMetadataBundle.getString("unableToShowProps"));
+ window.close();
+ }
+
+ if (elem.ownerDocument.getElementsByName && !elem.ownerDocument.namespaceURI)
+ htmlMode = true;
+
+ // htmllocalname is "" if it's not an html tag, or the name of the tag if it is.
+ var htmllocalname = "";
+ if (isHTMLElement(elem,"")) {
+ htmllocalname = elem.localName.toLowerCase();
+ }
+
+ // We only look for images once
+ checkForImage(elem, htmllocalname);
+
+ // Walk up the tree, looking for elements of interest.
+ // Each of them could be at a different level in the tree, so they each
+ // need their own boolean to tell us to stop looking.
+ while (elem && elem.nodeType == Node.ELEMENT_NODE) {
+ htmllocalname = "";
+ if (isHTMLElement(elem,"")) {
+ htmllocalname = elem.localName.toLowerCase();
+ }
+
+ if (!onLink) checkForLink(elem, htmllocalname);
+ if (!onInsDel) checkForInsDel(elem, htmllocalname);
+ if (!onQuote) checkForQuote(elem, htmllocalname);
+ if (!onTable) checkForTable(elem, htmllocalname);
+ if (!onTitle) checkForTitle(elem, htmllocalname);
+ if (!onLang) checkForLang(elem, htmllocalname);
+
+ elem = elem.parentNode;
+ }
+
+ // Decide which sections to show
+ var onMisc = onTable || onTitle || onLang;
+ if (!onMisc) hideNode("misc-sec");
+ if (!onLink) hideNode("link-sec");
+ if (!onImage) hideNode("image-sec");
+ if (!onInsDel) hideNode("insdel-sec");
+ if (!onQuote) hideNode("quote-sec");
+
+ // Fix the Misc section visibilities
+ if (onMisc) {
+ if (!onTable) hideNode("misc-tblsummary");
+ if (!onLang) hideNode("misc-lang");
+ if (!onTitle) hideNode("misc-title");
+ }
+
+ // Get rid of the "No properties" message. This is a backstop -
+ // it should really never show, as long as nsContextMenu.js's
+ // checking doesn't get broken.
+ if (onLink || onImage || onInsDel || onQuote || onMisc)
+ hideNode("no-properties")
+}
+
+var cacheListener = {
+ onCacheEntryAvailable: function onCacheEntryAvailable(descriptor) {
+ if (descriptor) {
+ var kbSize = descriptor.dataSize / 1024;
+ kbSize = Math.round(kbSize * 100) / 100;
+ setInfo("image-filesize", gMetadataBundle.getFormattedString("imageSize",
+ [formatNumber(kbSize),
+ formatNumber(descriptor.dataSize)]));
+ } else {
+ setInfo("image-filesize", gMetadataBundle.getString("imageSizeUnknown"));
+ }
+ },
+ onCacheEntryCheck: function onCacheEntryCheck() {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ }
+};
+
+function checkForImage(elem, htmllocalname)
+{
+ var img;
+ var imgType; // "img" = <img>
+ // "object" = <object>
+ // "input" = <input type=image>
+ // "background" = css background (to be added later)
+ var ismap = false;
+
+ if (htmllocalname === "img") {
+ img = elem;
+ imgType = "img";
+
+ } else if (htmllocalname === "object" &&
+ elem.type.substring(0,6) == "image/" &&
+ elem.data) {
+ img = elem;
+ imgType = "object";
+
+ } else if (htmllocalname === "input" &&
+ elem.type.toUpperCase() == "IMAGE") {
+ img = elem;
+ imgType = "input";
+
+ } else if (htmllocalname === "area" || htmllocalname === "a") {
+
+ // Clicked in image map?
+ var map = elem;
+ ismap = true;
+ setAlt(map);
+
+ while (map && map.nodeType == Node.ELEMENT_NODE && !isHTMLElement(map,"map") )
+ map = map.parentNode;
+
+ if (map && map.nodeType == Node.ELEMENT_NODE) {
+ img = getImageForMap(map);
+ var imgLocalName = img && img.localName.toLowerCase();
+ if (imgLocalName == "img" || imgLocalName == "object") {
+ imgType = imgLocalName;
+ }
+ }
+
+ }
+
+ if (img) {
+
+ var imgURL = imgType == "object" ? img.data : img.src;
+ setInfo("image-url", imgURL);
+
+ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ const LoadContextInfo = Services.loadContextInfo;
+ var loadContextInfo = opener.gPrivate ? LoadContextInfo.private :
+ LoadContextInfo.default;
+ const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+ Services.cache2
+ .getService(Ci.nsICacheStorageService)
+ .diskCacheStorage(loadContextInfo, false)
+ .asyncOpenURI(NetUtil.newURI(imgURL), null,
+ Ci.nsICacheStorage.OPEN_READONLY, cacheListener);
+
+ if ("width" in img && img.width != "") {
+ setInfo("image-width", gMetadataBundle.getFormattedString("imageWidth", [formatNumber(img.width)]));
+ setInfo("image-height", gMetadataBundle.getFormattedString("imageHeight", [formatNumber(img.height)]));
+ }
+ else {
+ setInfo("image-width", "");
+ setInfo("image-height", "");
+ }
+
+ if (imgType == "img") {
+ setInfo("image-desc", img.longDesc);
+ } else {
+ setInfo("image-desc", "");
+ }
+
+ onImage = true;
+ }
+
+ if (!ismap) {
+ if (imgType == "img" || imgType == "input") {
+ setAlt(img);
+ } else {
+ hideNode("image-alt");
+ }
+ }
+}
+
+function checkForLink(elem, htmllocalname)
+{
+ if ((htmllocalname === "a" && elem.href != "") ||
+ htmllocalname === "area") {
+
+ setLink(elem.href, elem.getAttribute("type"),
+ convertLanguageCode(elem.getAttribute("hreflang")),
+ elem.getAttribute("rel"), elem.getAttribute("rev"));
+
+ var target = elem.target;
+
+ switch (target) {
+ case "_top":
+ setInfo("link-target", gMetadataBundle.getString("sameWindowText"));
+ break;
+ case "_parent":
+ setInfo("link-target", gMetadataBundle.getString("parentFrameText"));
+ break;
+ case "_blank":
+ setInfo("link-target", gMetadataBundle.getString("newWindowText"));
+ break;
+ case "":
+ case "_self":
+ if (elem.ownerDocument.defaultView) {
+ if (elem.ownerDocument != elem.ownerDocument.defaultView.content.document)
+ setInfo("link-target", gMetadataBundle.getString("sameFrameText"));
+ else
+ setInfo("link-target", gMetadataBundle.getString("sameWindowText"));
+ } else {
+ hideNode("link-target");
+ }
+ break;
+ default:
+ setInfo("link-target", "\"" + target + "\"");
+ }
+
+ onLink = true;
+ }
+ else if (elem.namespaceURI == MathMLNS && elem.hasAttribute("href")) {
+ setLink(makeHrefAbsolute(elem.getAttribute("href"), elem));
+
+ setInfo("link-target", "");
+
+ onLink = true;
+ }
+ else if (elem.getAttributeNS(XLinkNS, "href")) {
+ setLink(makeHrefAbsolute(elem.getAttributeNS(XLinkNS, "href"), elem));
+
+ switch (elem.getAttributeNS(XLinkNS,"show")) {
+ case "embed":
+ setInfo("link-target", gMetadataBundle.getString("embeddedText"));
+ break;
+ case "new":
+ setInfo("link-target", gMetadataBundle.getString("newWindowText"));
+ break;
+ case null:
+ case "":
+ case "replace":
+ if (elem.ownerDocument != elem.ownerDocument.defaultView.content.document)
+ setInfo("link-target", gMetadataBundle.getString("sameFrameText"));
+ else
+ setInfo("link-target", gMetadataBundle.getString("sameWindowText"));
+ break;
+ default:
+ setInfo("link-target", "");
+ break;
+ }
+
+ onLink = true;
+ }
+}
+
+function checkForInsDel(elem, htmllocalname)
+{
+ if ((htmllocalname === "ins" || htmllocalname === "del") &&
+ (elem.cite || elem.dateTime)) {
+ setInfo("insdel-cite", elem.cite);
+ setInfo("insdel-date", elem.dateTime);
+ onInsDel = true;
+ }
+}
+
+
+function checkForQuote(elem, htmllocalname)
+{
+ if ((htmllocalname === "q" || htmllocalname === "blockquote") && elem.cite) {
+ setInfo("quote-cite", elem.cite);
+ onQuote = true;
+ }
+}
+
+function checkForTable(elem, htmllocalname)
+{
+ if (htmllocalname === "table" && elem.summary) {
+ setInfo("misc-tblsummary", elem.summary);
+ onTable = true;
+ }
+}
+
+function checkForLang(elem, htmllocalname)
+{
+ if ((htmllocalname && elem.lang) || elem.getAttributeNS(XMLNS, "lang")) {
+ var abbr;
+ if (htmllocalname && elem.lang)
+ abbr = elem.lang;
+ else
+ abbr = elem.getAttributeNS(XMLNS, "lang");
+
+ setInfo("misc-lang", convertLanguageCode(abbr));
+ onLang = true;
+ }
+}
+
+function checkForTitle(elem, htmllocalname)
+{
+ if (htmllocalname && elem.title) {
+ setInfo("misc-title", elem.title);
+ onTitle = true;
+ }
+}
+
+/*
+ * Set five link properties at once.
+ * All parameters are optional.
+ */
+function setLink(url, lang, type, rel, rev)
+{
+ setInfo("link-url", url);
+ setInfo("link-type", type);
+ setInfo("link-lang", lang);
+ setInfo("link-rel", rel);
+ setInfo("link-rev", rev);
+}
+
+/*
+ * Set text of node id to value
+ * if value="" the node with specified id is hidden.
+ * Node should be have one of these forms
+ * <xul:label id="id-text" value=""/>
+ * <xul:description id="id-text"/>
+ */
+function setInfo(id, value)
+{
+ if (!value) {
+ hideNode(id);
+ return;
+ }
+
+ var node = document.getElementById(id+"-text");
+
+ if (node.namespaceURI == XULNS && node.localName == "label" ||
+ (node.namespaceURI == XULNS && node.localName == "textbox")) {
+ node.setAttribute("value",value);
+
+ } else if (node.namespaceURI == XULNS && node.localName == "description") {
+ node.textContent = value;
+ }
+}
+
+// Hide node with specified id
+function hideNode(id)
+{
+ var style = document.getElementById(id).getAttribute("style");
+ document.getElementById(id).setAttribute("style", "display:none;" + style);
+}
+
+/*
+ * Find <img> or <object> which uses an imagemap.
+ * If more then one object is found we can't determine which one
+ * was clicked.
+ *
+ * This code has to be changed once bug 1882 is fixed.
+ * Once bug 72527 is fixed this code should use the .images collection.
+ */
+function getImageForMap(map)
+{
+ var mapuri = "#" + map.getAttribute("name");
+ var multipleFound = false;
+ var img;
+
+ var list = getHTMLElements(map.ownerDocument, "img");
+ for (var i=0; i < list.length; i++) {
+ if (list.item(i).getAttribute("usemap") == mapuri) {
+ if (img) {
+ multipleFound = true;
+ break;
+ } else {
+ img = list.item(i);
+ imgType = "img";
+ }
+ }
+ }
+
+ list = getHTMLElements(map.ownerDocument, "object");
+ for (i = 0; i < list.length; i++) {
+ if (list.item(i).getAttribute("usemap") == mapuri) {
+ if (img) {
+ multipleFound = true;
+ break;
+ } else {
+ img = list.item(i);
+ imgType = "object";
+ }
+ }
+ }
+
+ if (multipleFound)
+ img = null;
+
+ return img;
+}
+
+function getHTMLElements(node, name)
+{
+ if (htmlMode)
+ return node.getElementsByTagName(name);
+ return node.getElementsByTagNameNS(XHTMLNS, name);
+}
+
+// name should be in lower case
+function isHTMLElement(node, name)
+{
+ if (node.nodeType != Node.ELEMENT_NODE)
+ return false;
+
+ if (htmlMode)
+ return !name || node.localName.toLowerCase() == name;
+
+ return (!name || node.localName == name) && node.namespaceURI == XHTMLNS;
+}
+
+// This function coded according to the spec at:
+// http://www.bath.ac.uk/~py8ieh/internet/discussion/metadata.txt
+function convertLanguageCode(abbr)
+{
+ if (!abbr) return "";
+ var result;
+ var region = "";
+ var tokens = abbr.split("-");
+ var language = tokens.shift();
+
+ if (language == "x" || language == "i")
+ {
+ // x and i prefixes mean unofficial ones. So we proper-case the next
+ // word and leave the rest.
+ if (tokens.length > 0)
+ {
+ // Upper-case first letter
+ language = tokens[0].substr(0, 1).toUpperCase() + tokens[0].substr(1);
+ tokens.shift();
+
+ // Add on the rest as space-separated strings inside the brackets
+ region = tokens.join(" ");
+ }
+ }
+ else
+ {
+ // Otherwise we treat the first as a lang, the second as a region
+ // and the rest as strings.
+ try
+ {
+ language = gLangBundle.getString(language.toLowerCase());
+ }
+ catch (e)
+ {
+ }
+
+ if (tokens.length > 0)
+ {
+ try
+ {
+ tokens[0] = gRegionBundle.getString(tokens[0].toLowerCase());
+ }
+ catch (e)
+ {
+ }
+ region = tokens.join(" ");
+ }
+ }
+
+ if (region) {
+ result = gMetadataBundle.getFormattedString("languageRegionFormat",
+ [language, region]);
+ } else {
+ result = language;
+ }
+ return result;
+}
+
+function setAlt(elem) {
+ var altText = document.getElementById("image-alt-text");
+ if (elem.hasAttribute("alt")) {
+ if (elem.alt != "") {
+ altText.value = elem.alt;
+ altText.setAttribute("style","font-style:inherit");
+ } else {
+ altText.value = gMetadataBundle.getString("altTextBlank");
+ altText.setAttribute("style","font-style:italic");
+ }
+ } else {
+ altText.value = gMetadataBundle.getString("altTextMissing");
+ altText.setAttribute("style","font-style:italic");
+ }
+
+}
+
+function formatNumber(number)
+{
+ return (+number).toLocaleString(); // coerce number to a numeric value before calling toLocaleString()
+}
+
+function makeHrefAbsolute(href, elem)
+{
+ const {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+ try {
+ var baseURI = NetUtil.newURI(elem.baseURI, elem.ownerDocument.characterSet);
+ href = NetUtil.newURI(href, elem.ownerDocument.characterSet, baseURI).spec;
+ } catch (e) {
+ }
+ return href;
+}
diff --git a/comm/suite/browser/metadata.xul b/comm/suite/browser/metadata.xul
new file mode 100644
index 0000000000..43d0f7b00b
--- /dev/null
+++ b/comm/suite/browser/metadata.xul
@@ -0,0 +1,192 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://navigator/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://navigator/skin/pageInfo.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+ <!ENTITY % metadataDTD SYSTEM "chrome://navigator/locale/metadata.dtd" >
+ %metadataDTD;
+]>
+
+<dialog id="metadata"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&caption.label;"
+ onload="onLoad()"
+ minwidth="350"
+ buttons="none"
+ persist="screenX screenY"
+ screenX="24" screenY="24">
+
+ <script src="chrome://navigator/content/metadata.js"/>
+
+ <stringbundle src="chrome://navigator/locale/metadata.properties" id="bundle_metadata"/>
+ <stringbundle src="chrome://global/locale/languageNames.properties" id="bundle_languages"/>
+ <stringbundle src="chrome://global/locale/regionNames.properties" id="bundle_regions"/>
+
+ <label id="no-properties" value="&no-properties.label;"/>
+
+ <vbox id="link-sec">
+ <label value="&link-sec.label;"/>
+ <separator class="groove"/>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row id="link-url">
+ <separator orient="vertical"/>
+ <label value="&link-url.label; " control="link-url-text"/>
+ <textbox readonly="true" id="link-url-text" class="meta-properties"/>
+ </row>
+ <row id="link-target">
+ <separator orient="vertical"/>
+ <label value="&link-target.label; " control="link-target-text"/>
+ <textbox readonly="true" id="link-target-text" class="meta-properties"/>
+ </row>
+ <row id="link-type">
+ <separator orient="vertical"/>
+ <label value="&link-type.label; " control="link-type-text"/>
+ <textbox readonly="true" id="link-type-text" class="meta-properties"/>
+ </row>
+ <row id="link-lang">
+ <separator orient="vertical"/>
+ <label value="&link-lang.label; " control="link-lang-text"/>
+ <textbox readonly="true" id="link-lang-text" class="meta-properties"/>
+ </row>
+ <row id="link-rel">
+ <separator orient="vertical"/>
+ <label value="&link-rel.label; " control="link-rel-text"/>
+ <textbox readonly="true" id="link-rel-text" class="meta-properties"/>
+ </row>
+ <row id="link-rev">
+ <separator orient="vertical"/>
+ <label value="&link-rev.label; " control="link-rev-text"/>
+ <textbox readonly="true" id="link-rev-text" class="meta-properties"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ </vbox>
+ <vbox id="image-sec">
+ <label value="&image-sec.label;"/>
+ <separator class="groove"/>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row id="image-url">
+ <separator orient="vertical"/>
+ <label value="&image-url.label; " control="image-url-text"/>
+ <textbox readonly="true" id="image-url-text" class="meta-properties"/>
+ </row>
+ <row id="image-width">
+ <separator orient="vertical"/>
+ <label value="&image-width.label; " control="image-width-text"/>
+ <textbox readonly="true" id="image-width-text" class="meta-properties"/>
+ </row>
+ <row id="image-height">
+ <separator orient="vertical"/>
+ <label value="&image-height.label; " control="image-height-text"/>
+ <textbox readonly="true" id="image-height-text" class="meta-properties"/>
+ </row>
+ <row id="image-filesize">
+ <separator orient="vertical"/>
+ <label value="&image-filesize.label; " control="image-filesize-text"/>
+ <textbox readonly="true" id="image-filesize-text" value="&image-filesize.value;" class="meta-properties"/>
+ </row>
+ <row id="image-alt">
+ <separator orient="vertical"/>
+ <label value="&image-alt.label; " control="image-alt-text"/>
+ <textbox readonly="true" id="image-alt-text" class="meta-properties"/>
+ </row>
+ <row id="image-desc">
+ <separator orient="vertical"/>
+ <label value="&image-desc.label; " control="image-desc-text"/>
+ <textbox readonly="true" id="image-desc-text" class="meta-properties"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ </vbox>
+ <vbox id="insdel-sec">
+ <label value="&insdel-sec.label;"/>
+ <separator class="groove"/>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row id="insdel-cite">
+ <separator orient="vertical"/>
+ <label value="&insdel-cite.label; " control="insdel-cite-text"/>
+ <textbox readonly="true" id="insdel-cite-text" class="meta-properties"/>
+ </row>
+ <row id="insdel-date">
+ <separator orient="vertical"/>
+ <label value="&insdel-date.label; " control="insdel-date-text"/>
+ <textbox readonly="true" id="insdel-date-text" class="meta-properties"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ </vbox>
+ <vbox id="quote-sec">
+ <label value="&quote-sec.label;"/>
+ <separator class="groove"/>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row id="quote-cite">
+ <separator orient="vertical"/>
+ <label value="&quote-cite.label; " control="quote-cite-text"/>
+ <textbox readonly="true" id="quote-cite-text" class="meta-properties"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ </vbox>
+ <vbox id="misc-sec">
+ <label value="&misc-sec.label;"/>
+ <separator class="groove"/>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row id="misc-lang">
+ <separator orient="vertical"/>
+ <label value="&misc-lang.label; " control="misc-lang-text"/>
+ <textbox readonly="true" id="misc-lang-text" class="meta-properties"/>
+ </row>
+ <row id="misc-title">
+ <separator orient="vertical"/>
+ <label value="&misc-title.label; " control="misc-title-text"/>
+ <textbox readonly="true" id="misc-title-text" class="meta-properties"/>
+ </row>
+ <row id="misc-tblsummary">
+ <separator orient="vertical"/>
+ <label value="&misc-tblsummary.label; " control="misc-tblsummary-text"/>
+ <textbox readonly="true" id="misc-tblsummary-text" class="meta-properties"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ </vbox>
+ </dialog>
diff --git a/comm/suite/browser/moz.build b/comm/suite/browser/moz.build
new file mode 100644
index 0000000000..6740f038ae
--- /dev/null
+++ b/comm/suite/browser/moz.build
@@ -0,0 +1,22 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += ["test/browser/browser.ini"]
+MOCHITEST_CHROME_MANIFESTS += ["test/chrome/chrome.ini"]
+MOCHITEST_MANIFESTS += ["test/mochitest/mochitest.ini"]
+
+EXTRA_COMPONENTS += [
+ "nsBrowserContentHandler.js",
+ "nsTypeAheadFind.js",
+ "SuiteBrowser.manifest",
+]
+
+JAR_MANIFESTS += ["jar.mn"]
+
+for var in ("MOZ_APP_NAME", "MOZ_APP_DISPLAYNAME", "MOZ_APP_VERSION"):
+ DEFINES[var] = '"%s"' % CONFIG[var]
+
+if CONFIG["MOZILLA_OFFICIAL"]:
+ DEFINES["OFFICIAL_BUILD"] = 1
diff --git a/comm/suite/browser/navigator.css b/comm/suite/browser/navigator.css
new file mode 100644
index 0000000000..8b9c11ada3
--- /dev/null
+++ b/comm/suite/browser/navigator.css
@@ -0,0 +1,135 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+/* ::::: Hide the link toolbar if it is set to autohide and has no items. ::::: */
+
+#linktoolbar[hidden="maybe"][hasitems="false"] {
+ display: none;
+}
+
+/* ::::: tabbed browser ::::: */
+
+tabbrowser {
+ -moz-binding: url("chrome://navigator/content/tabbrowser.xml#tabbrowser");
+}
+
+.tabbrowser-tabs {
+ -moz-binding: url("chrome://navigator/content/tabbrowser.xml#tabbrowser-tabs");
+}
+
+.tabbrowser-tab:-moz-lwtheme:not([customization-lwtheme]) {
+ color: var(--lwt-text-color);
+ text-shadow: var(--lwt-accent-color);
+}
+
+.tabbrowser-arrowscrollbox {
+ -moz-binding: url("chrome://navigator/content/tabbrowser.xml#tabbrowser-arrowscrollbox");
+}
+
+.tabs-alltabs-popup {
+ -moz-binding: url("chrome://navigator/content/tabbrowser.xml#tabbrowser-alltabs-popup");
+}
+
+.tabs-closebutton-box > .tabs-closebutton {
+ -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton");
+}
+
+/* ::::: search suggestions autocomplete ::::: */
+#PopupAutoComplete > richlistbox > richlistitem > .ac-type-icon,
+#PopupAutoComplete > richlistbox > richlistitem > .ac-site-icon,
+#PopupAutoComplete > richlistbox > richlistitem > .ac-tags,
+#PopupAutoComplete > richlistbox > richlistitem > .ac-separator,
+#PopupAutoComplete > richlistbox > richlistitem > .ac-url {
+ display: none;
+}
+
+/* ::::: urlbar autocomplete ::::: */
+
+#urlbar {
+ -moz-binding: url("chrome://navigator/content/urlbarBindings.xml#urlbar");
+}
+
+.paste-and-go {
+ -moz-binding: url("chrome://navigator/content/urlbarBindings.xml#input-box-paste");
+}
+
+panel[for="urlbar"] {
+ -moz-binding: url("chrome://navigator/content/urlbarBindings.xml#autocomplete-result-popup") !important;
+}
+
+.autocomplete-search-box {
+ -moz-binding: url("chrome://navigator/content/urlbarBindings.xml#autocomplete-search-box");
+}
+
+.autocomplete-search-engine {
+ -moz-binding: url("chrome://navigator/content/urlbarBindings.xml#autocomplete-search-engine");
+ -moz-box-align: center;
+}
+
+#DateTimePickerPanel[active="true"] {
+ -moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-popup");
+}
+
+.popup-anchor {
+ /* should occupy space but not be visible */
+ opacity: 0;
+ pointer-events: none;
+ -moz-stack-sizing: ignore;
+}
+
+/* ::::: search bar ::::: */
+searchbar {
+ -moz-binding: url("chrome://communicator/content/search/search.xml#searchbar");
+}
+
+#wrapper-search-container > #search-container > #searchbar > .searchbar-textbox > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input {
+ visibility: hidden;
+}
+
+/* ::::: bookmarks menu ::::: */
+
+.isempty:not(:last-child) {
+ display: none;
+}
+
+/* ::::: bookmarks toolbar ::::: */
+
+#wrapper-personal-bookmarks[place="palette"] > toolbaritem > #PlacesToolbar {
+ display: none;
+}
+
+/* notification anchors should only be visible when their associated
+ notifications are */
+.notification-anchor-icon {
+ -moz-user-focus: normal;
+}
+
+.notification-anchor-icon:not([showing]) {
+ display: none;
+}
+
+popupnotification-centeritem {
+ -moz-binding: url("chrome://communicator/content/bindings/notification.xml#center-item");
+}
+
+#addon-install-started-notification {
+ -moz-binding: url("chrome://communicator/content/bindings/notification.xml#addon-progress-popup-notification");
+}
+
+/* ::::: Wallpaper fix for Bug 435652. Remove when Bug 204743 is fixed ::::: */
+
+.textbox-input-box {
+ overflow-x: hidden;
+}
+
+/* Developer Tools: Error counter */
+
+#developer-toolbar-toolbox-button[error-count]:before {
+ content: attr(error-count);
+ display: -moz-box;
+ -moz-box-pack: center;
+}
diff --git a/comm/suite/browser/navigator.js b/comm/suite/browser/navigator.js
new file mode 100644
index 0000000000..2321906fb9
--- /dev/null
+++ b/comm/suite/browser/navigator.js
@@ -0,0 +1,3311 @@
+/* -*- 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 {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+const {AeroPeek} = ChromeUtils.import("resource:///modules/WindowsPreviewPerTab.jsm");
+var {AppConstants} = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ NetUtil: "resource://gre/modules/NetUtil.jsm",
+ PluralForm: "resource://gre/modules/PluralForm.jsm",
+ PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
+ PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
+ SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm",
+ SitePermissions: "resource:///modules/SitePermissions.jsm",
+});
+
+XPCOMUtils.defineLazyScriptGetter(this, "gEditItemOverlay",
+ "chrome://communicator/content/places/editBookmarkOverlay.js");
+
+const REMOTESERVICE_CONTRACTID = "@mozilla.org/toolkit/remote-service;1";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const nsIWebNavigation = Ci.nsIWebNavigation;
+
+var gPrintSettingsAreGlobal = true;
+var gSavePrintSettings = true;
+var gChromeState = null; // chrome state before we went into print preview
+var gInPrintPreviewMode = false;
+var gNavToolbox = null;
+var gFindInstData;
+var gURLBar = null;
+var gProxyButton = null;
+var gProxyFavIcon = null;
+var gProxyDeck = null;
+var gNavigatorBundle;
+var gBrandBundle;
+var gNavigatorRegionBundle;
+var gLastValidURLStr = "";
+var gLastValidURL = null;
+var gClickSelectsAll = false;
+var gClickAtEndSelects = false;
+var gIgnoreFocus = false;
+var gIgnoreClick = false;
+
+// Listeners for updating zoom value in status bar
+var ZoomListeners =
+{
+
+ // Identifies the setting in the content prefs database.
+ name: "browser.content.full-zoom",
+
+ QueryInterface:
+ XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsIContentPrefObserver,
+ Ci.nsIContentPrefCallback2,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISupports]),
+
+ init: function ()
+ {
+ Cc["@mozilla.org/content-pref/service;1"]
+ .getService(Ci.nsIContentPrefService2)
+ .addObserverForName(this.name, this);
+
+ Services.prefs.addObserver("browser.zoom.", this, true);
+ this.updateVisibility();
+ },
+
+ destroy: function ()
+ {
+ Cc["@mozilla.org/content-pref/service;1"]
+ .getService(Ci.nsIContentPrefService2)
+ .removeObserverForName(this.name, this);
+
+ Services.prefs.removeObserver("browser.zoom.", this);
+ },
+
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "nsPref:changed"){
+ switch (aData) {
+ case "browser.zoom.siteSpecific":
+ updateZoomStatus();
+ break;
+ case "browser.zoom.showZoomStatusPanel":
+ this.updateVisibility();
+ break;
+ }
+ }
+ },
+
+ updateVisibility: function()
+ {
+ let hidden = !Services.prefs.getBoolPref("browser.zoom.showZoomStatusPanel");
+ document.getElementById("zoomOut-button").setAttribute('hidden', hidden);
+ document.getElementById("zoomIn-button").setAttribute('hidden', hidden);
+ document.getElementById("zoomLevel-display").setAttribute('hidden', hidden);
+ },
+
+ onContentPrefSet: function (aGroup, aName, aValue)
+ {
+ // Make sure zoom values loaded before updating
+ window.setTimeout(updateZoomStatus, 0);
+ },
+
+ onContentPrefRemoved: function (aGroup, aName)
+ {
+ // Make sure zoom values loaded before updating
+ window.setTimeout(updateZoomStatus, 0);
+ },
+
+ handleResult: function(aPref)
+ {
+ updateZoomStatus();
+ },
+
+ onLocationChange: function(aURI)
+ {
+ updateZoomStatus();
+ }
+};
+
+var gInitialPages = new Set([
+ "about:blank",
+ "about:privatebrowsing",
+ "about:sessionrestore"
+]);
+
+//cached elements
+var gBrowser = null;
+
+var gTabStripPrefListener =
+{
+ domain: "browser.tabs.autoHide",
+ observe: function(subject, topic, prefName)
+ {
+ // verify that we're changing the tab browser strip auto hide pref
+ if (topic != "nsPref:changed")
+ return;
+
+ if (gBrowser.tabContainer.childNodes.length == 1 && window.toolbar.visible) {
+ var stripVisibility = !Services.prefs.getBoolPref(prefName);
+ gBrowser.setStripVisibilityTo(stripVisibility);
+ Services.prefs.setBoolPref("browser.tabs.forceHide", false);
+ }
+ }
+};
+
+var gHomepagePrefListener =
+{
+ domain: "browser.startup.homepage",
+ observe: function(subject, topic, prefName)
+ {
+ // verify that we're changing the home page pref
+ if (topic != "nsPref:changed")
+ return;
+
+ updateHomeButtonTooltip();
+ }
+};
+
+var gStatusBarPopupIconPrefListener =
+{
+ domain: "privacy.popups.statusbar_icon_enabled",
+ observe: function(subject, topic, prefName)
+ {
+ if (topic != "nsPref:changed" || prefName != this.domain)
+ return;
+
+ var popupIcon = document.getElementById("popupIcon");
+ if (!Services.prefs.getBoolPref(prefName))
+ popupIcon.hidden = true;
+
+ else if (gBrowser.getNotificationBox().popupCount)
+ popupIcon.hidden = false;
+ }
+};
+
+var gFormSubmitObserver = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver,
+ Ci.nsIObserver]),
+
+ panel: null,
+
+ init: function()
+ {
+ this.panel = document.getElementById("invalid-form-popup");
+ },
+
+ notifyInvalidSubmit: function (aFormElement, aInvalidElements)
+ {
+ // We are going to handle invalid form submission attempt by focusing the
+ // first invalid element and show the corresponding validation message in a
+ // panel attached to the element.
+ if (!aInvalidElements.length) {
+ return;
+ }
+
+ // Don't show the popup if the current tab doesn't contain the invalid form.
+ if (aFormElement.ownerDocument.defaultView.top != content) {
+ return;
+ }
+
+ let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
+
+ if (!(element instanceof HTMLInputElement ||
+ element instanceof HTMLTextAreaElement ||
+ element instanceof HTMLSelectElement ||
+ element instanceof HTMLButtonElement)) {
+ return;
+ }
+
+ this.panel.firstChild.textContent = element.validationMessage;
+
+ element.focus();
+
+ // If the user interacts with the element and makes it valid or leaves it,
+ // we want to remove the popup.
+ // We could check for clicks but a click already removes the popup.
+ function blurHandler() {
+ gFormSubmitObserver.panel.hidePopup();
+ }
+ function inputHandler(e) {
+ if (e.originalTarget.validity.valid) {
+ gFormSubmitObserver.panel.hidePopup();
+ } else {
+ // If the element is now invalid for a new reason, we should update the
+ // error message.
+ if (gFormSubmitObserver.panel.firstChild.textContent !=
+ e.originalTarget.validationMessage) {
+ gFormSubmitObserver.panel.firstChild.textContent =
+ e.originalTarget.validationMessage;
+ }
+ }
+ }
+ element.addEventListener("input", inputHandler);
+ element.addEventListener("blur", blurHandler);
+
+ // One event to bring them all and in the darkness bind them.
+ this.panel.addEventListener("popuphiding", function popupHidingHandler(aEvent) {
+ aEvent.target.removeEventListener("popuphiding", popupHidingHandler);
+ element.removeEventListener("input", inputHandler);
+ element.removeEventListener("blur", blurHandler);
+ });
+
+ this.panel.hidden = false;
+
+ var win = element.ownerDocument.defaultView;
+ var style = win.getComputedStyle(element, null);
+ var scale = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .fullZoom;
+
+ var offset = style.direction == 'rtl' ? parseInt(style.paddingRight) +
+ parseInt(style.borderRightWidth) :
+ parseInt(style.paddingLeft) +
+ parseInt(style.borderLeftWidth);
+
+ offset = Math.round(offset * scale);
+ this.panel.openPopup(element, "after_start", offset, 0);
+ }
+};
+
+/**
+* Pref listener handler functions.
+* Both functions assume that observer.domain is set to
+* the pref domain we want to start/stop listening to.
+*/
+function addPrefListener(observer)
+{
+ try {
+ Services.prefs.addObserver(observer.domain, observer);
+ } catch(ex) {
+ dump("Failed to observe prefs: " + ex + "\n");
+ }
+}
+
+function removePrefListener(observer)
+{
+ try {
+ Services.prefs.removeObserver(observer.domain, observer);
+ } catch(ex) {
+ dump("Failed to remove pref observer: " + ex + "\n");
+ }
+}
+
+function addPopupPermListener(observer)
+{
+ Services.obs.addObserver(observer, "popup-perm-close");
+}
+
+function removePopupPermListener(observer)
+{
+ Services.obs.removeObserver(observer, "popup-perm-close");
+}
+
+function addFormSubmitObserver(observer)
+{
+ observer.init();
+ Services.obs.addObserver(observer, "invalidformsubmit");
+}
+
+function removeFormSubmitObserver(observer)
+{
+ Services.obs.removeObserver(observer, "invalidformsubmit");
+}
+
+/**
+* We can avoid adding multiple load event listeners and save some time by adding
+* one listener that calls all real handlers.
+*/
+
+function pageShowEventHandlers(event)
+{
+ checkForDirectoryListing();
+}
+
+/**
+ * Determine whether or not the content area is displaying a page with frames,
+ * and if so, toggle the display of the 'save frame as' menu item.
+ **/
+function getContentAreaFrameCount()
+{
+ var saveFrameItem = document.getElementById("saveframe");
+ if (!content || !content.frames.length || !isContentFrame(document.commandDispatcher.focusedWindow))
+ saveFrameItem.setAttribute("hidden", "true");
+ else {
+ var autoDownload = Services.prefs.getBoolPref("browser.download.useDownloadDir");
+ goSetMenuValue("saveframe", autoDownload ? "valueSave" : "valueSaveAs");
+ saveFrameItem.removeAttribute("hidden");
+ }
+}
+
+function saveFrameDocument()
+{
+ var focusedWindow = document.commandDispatcher.focusedWindow;
+ if (isContentFrame(focusedWindow))
+ saveDocument(focusedWindow.document, true);
+}
+
+function updateHomeButtonTooltip()
+{
+ var homePage = getHomePage();
+ var tooltip = document.getElementById("home-button-tooltip-inner");
+
+ while (tooltip.hasChildNodes())
+ tooltip.lastChild.remove();
+
+ for (var i in homePage) {
+ var label = document.createElementNS(XUL_NS, "label");
+ label.setAttribute("value", homePage[i]);
+ tooltip.appendChild(label);
+ }
+}
+function getWebNavigation()
+{
+ try {
+ return getBrowser().webNavigation;
+ } catch (e) {
+ return null;
+ }
+}
+
+function BrowserReloadWithFlags(reloadFlags)
+{
+ // Reset temporary permissions on the current tab. This is done here
+ // because we only want to reset permissions on user reload.
+ SitePermissions.clearTemporaryPermissions(gBrowser.selectedBrowser);
+
+ // First, we'll try to use the session history object to reload so
+ // that framesets are handled properly. If we're in a special
+ // window (such as view-source) that has no session history, fall
+ // back on using the web navigation's reload method.
+ let webNav = getWebNavigation();
+ try {
+ let sh = webNav.sessionHistory;
+ if (sh)
+ webNav = sh.QueryInterface(Components.interfaces.nsIWebNavigation);
+ } catch (e) {
+ }
+
+ try {
+ webNav.reload(reloadFlags);
+ } catch (e) {
+ }
+}
+
+function toggleAffectedChrome(aHide)
+{
+ // chrome to toggle includes:
+ // (*) menubar
+ // (*) navigation bar
+ // (*) personal toolbar
+ // (*) tab browser ``strip''
+ // (*) sidebar
+ // (*) statusbar
+ // (*) findbar
+
+ if (!gChromeState)
+ gChromeState = new Object;
+
+ var statusbar = document.getElementById("status-bar");
+ getNavToolbox().hidden = aHide;
+ var notificationBox = gBrowser.getNotificationBox();
+ var findbar = document.getElementById("FindToolbar")
+
+ // sidebar states map as follows:
+ // hidden => hide/show nothing
+ // collapsed => hide/show only the splitter
+ // shown => hide/show the splitter and the box
+ if (aHide)
+ {
+ // going into print preview mode
+ gChromeState.sidebar = SidebarGetState();
+ SidebarSetState("hidden");
+
+ // deal with tab browser
+ gBrowser.mStrip.setAttribute("moz-collapsed", "true");
+
+ // deal with the Status Bar
+ gChromeState.statusbarWasHidden = statusbar.hidden;
+ statusbar.hidden = true;
+
+ // deal with the notification box
+ gChromeState.notificationsWereHidden = notificationBox.notificationsHidden;
+ notificationBox.notificationsHidden = true;
+
+ if (findbar)
+ {
+ gChromeState.findbarWasHidden = findbar.hidden;
+ findbar.close();
+ }
+ else
+ {
+ gChromeState.findbarWasHidden = true;
+ }
+
+ gChromeState.syncNotificationsOpen = false;
+ var syncNotifications = document.getElementById("sync-notifications");
+ if (syncNotifications)
+ {
+ gChromeState.syncNotificationsOpen = !syncNotifications.notificationsHidden;
+ syncNotifications.notificationsHidden = true;
+ }
+ }
+ else
+ {
+ // restoring normal mode (i.e., leaving print preview mode)
+ SidebarSetState(gChromeState.sidebar);
+
+ // restore tab browser
+ gBrowser.mStrip.removeAttribute("moz-collapsed");
+
+ // restore the Status Bar
+ statusbar.hidden = gChromeState.statusbarWasHidden;
+
+ // restore the notification box
+ notificationBox.notificationsHidden = gChromeState.notificationsWereHidden;
+
+ if (!gChromeState.findbarWasHidden)
+ findbar.open();
+
+ if (gChromeState.syncNotificationsOpen)
+ document.getElementById("sync-notifications").notificationsHidden = false;
+ }
+
+ // if we are unhiding and sidebar used to be there rebuild it
+ if (!aHide && gChromeState.sidebar == "visible")
+ SidebarRebuild();
+}
+
+var PrintPreviewListener = {
+ _printPreviewTab: null,
+ _tabBeforePrintPreview: null,
+
+ getPrintPreviewBrowser: function () {
+ if (!this._printPreviewTab) {
+ this._tabBeforePrintPreview = getBrowser().selectedTab;
+ this._printPreviewTab = getBrowser().addTab("about:blank");
+ getBrowser().selectedTab = this._printPreviewTab;
+ }
+ return getBrowser().getBrowserForTab(this._printPreviewTab);
+ },
+ getSourceBrowser: function () {
+ return this._tabBeforePrintPreview ?
+ getBrowser().getBrowserForTab(this._tabBeforePrintPreview) :
+ getBrowser().selectedBrowser;
+ },
+ getNavToolbox: function () {
+ return window.getNavToolbox();
+ },
+ onEnter: function () {
+ gInPrintPreviewMode = true;
+ toggleAffectedChrome(true);
+ },
+ onExit: function () {
+ getBrowser().selectedTab = this._tabBeforePrintPreview;
+ this._tabBeforePrintPreview = null;
+ gInPrintPreviewMode = false;
+ toggleAffectedChrome(false);
+ getBrowser().removeTab(this._printPreviewTab, { disableUndo: true });
+ this._printPreviewTab = null;
+ }
+};
+
+function getNavToolbox()
+{
+ if (!gNavToolbox)
+ gNavToolbox = document.getElementById("navigator-toolbox");
+ return gNavToolbox;
+}
+
+function BrowserPrintPreview()
+{
+ PrintUtils.printPreview(PrintPreviewListener);
+}
+
+function BrowserSetCharacterSet(aEvent)
+{
+ if (aEvent.target.hasAttribute("charset"))
+ getBrowser().docShell.charset = aEvent.target.getAttribute("charset");
+ BrowserCharsetReload();
+}
+
+function BrowserCharsetReload()
+{
+ BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
+}
+
+function BrowserUpdateCharsetMenu(aNode)
+{
+ var wnd = document.commandDispatcher.focusedWindow;
+ if (wnd.top != content)
+ wnd = content;
+ UpdateCharsetMenu(wnd.document.characterSet, aNode);
+}
+
+function EnableCharsetMenu()
+{
+ var menuitem = document.getElementById("charsetMenu");
+ if (getBrowser() && getBrowser().docShell &&
+ getBrowser().docShell.mayEnableCharacterEncodingMenu)
+ menuitem.removeAttribute("disabled");
+ else
+ menuitem.setAttribute("disabled", "true");
+}
+
+function getFindInstData()
+{
+ if (!gFindInstData) {
+ gFindInstData = new nsFindInstData();
+ gFindInstData.browser = getBrowser();
+ // defaults for rootSearchWindow and currentSearchWindow are fine here
+ }
+ return gFindInstData;
+}
+
+function BrowserFind()
+{
+ findInPage(getFindInstData());
+}
+
+function BrowserFindAgain(reverse)
+{
+ findAgainInPage(getFindInstData(), reverse);
+}
+
+function BrowserCanFindAgain()
+{
+ return canFindAgainInPage();
+}
+
+function getMarkupDocumentViewer()
+{
+ return getBrowser().markupDocumentViewer;
+}
+
+function getBrowser()
+{
+ if (!gBrowser)
+ gBrowser = document.getElementById("content");
+ return gBrowser;
+}
+
+function getHomePage()
+{
+ var URIs = [];
+ URIs[0] = GetLocalizedStringPref("browser.startup.homepage");
+ var count = Services.prefs.getIntPref("browser.startup.homepage.count");
+ for (var i = 1; i < count; ++i)
+ URIs[i] = GetLocalizedStringPref("browser.startup.homepage." + i);
+
+ return URIs;
+}
+
+function UpdateBackForwardButtons()
+{
+ var backBroadcaster = document.getElementById("canGoBack");
+ var forwardBroadcaster = document.getElementById("canGoForward");
+ var upBroadcaster = document.getElementById("canGoUp");
+ var browser = getBrowser();
+
+ // Avoid setting attributes on broadcasters if the value hasn't changed!
+ // Remember, guys, setting attributes on elements is expensive! They
+ // get inherited into anonymous content, broadcast to other widgets, etc.!
+ // Don't do it if the value hasn't changed! - dwh
+
+ var backDisabled = backBroadcaster.hasAttribute("disabled");
+ var forwardDisabled = forwardBroadcaster.hasAttribute("disabled");
+ var upDisabled = upBroadcaster.hasAttribute("disabled");
+ if (backDisabled == browser.canGoBack) {
+ if (backDisabled)
+ backBroadcaster.removeAttribute("disabled");
+ else
+ backBroadcaster.setAttribute("disabled", true);
+ }
+ if (forwardDisabled == browser.canGoForward) {
+ if (forwardDisabled)
+ forwardBroadcaster.removeAttribute("disabled");
+ else
+ forwardBroadcaster.setAttribute("disabled", true);
+ }
+ if (upDisabled != !browser.currentURI.spec.replace(/[#?].*$/, "").match(/\/[^\/]+\/./)) {
+ if (upDisabled)
+ upBroadcaster.removeAttribute("disabled");
+ else
+ upBroadcaster.setAttribute("disabled", true);
+ }
+}
+
+const nsIBrowserDOMWindow = Ci.nsIBrowserDOMWindow;
+const nsIInterfaceRequestor = Ci.nsIInterfaceRequestor;
+
+function nsBrowserAccess() {
+}
+
+nsBrowserAccess.prototype = {
+ createContentWindow(aURI, aOpener, aWhere, aFlags, aTriggeringPrincipal = null) {
+ return this.getContentWindowOrOpenURI(null, aOpener, aWhere, aFlags,
+ aTriggeringPrincipal);
+ },
+
+ openURI: function (aURI, aOpener, aWhere, aFlags, aTriggeringPrincipal = null) {
+ if (!aURI) {
+ Cu.reportError("openURI should only be called with a valid URI");
+ throw Cr.NS_ERROR_FAILURE;
+ }
+ return this.getContentWindowOrOpenURI(aURI, aOpener, aWhere, aFlags,
+ aTriggeringPrincipal);
+ },
+
+ getContentWindowOrOpenURI(aURI, aOpener, aWhere, aFlags, aTriggeringPrincipal) {
+ var isExternal = !!(aFlags & nsIBrowserDOMWindow.OPEN_EXTERNAL);
+
+ if (aOpener && isExternal) {
+ Cu.reportError("nsBrowserAccess.openURI did not expect an opener to be " +
+ "passed if the context is OPEN_EXTERNAL.");
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ if (aWhere == nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
+ if (isExternal)
+ aWhere = Services.prefs.getIntPref("browser.link.open_external");
+ else
+ aWhere = Services.prefs.getIntPref("browser.link.open_newwindow");
+ }
+
+ let referrer = aOpener ? aOpener.QueryInterface(nsIInterfaceRequestor)
+ .getInterface(nsIWebNavigation)
+ .currentURI : null;
+ let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_UNSET;
+ if (aOpener && aOpener.document) {
+ referrerPolicy = aOpener.document.referrerPolicy;
+ }
+ var uri = aURI ? aURI.spec : "about:blank";
+
+ switch (aWhere) {
+ case nsIBrowserDOMWindow.OPEN_NEWWINDOW:
+ return window.openDialog(getBrowserURL(), "_blank", "all,dialog=no",
+ uri, null, null);
+ case nsIBrowserDOMWindow.OPEN_NEWTAB:
+ var bgLoad = Services.prefs.getBoolPref("browser.tabs.loadDivertedInBackground");
+ var isRelated = referrer ? true : false;
+ // If we have an opener, that means that the caller is expecting access
+ // to the nsIDOMWindow of the opened tab right away.
+ let userContextId = aOpener && aOpener.document
+ ? aOpener.document.nodePrincipal.originAttributes.userContextId
+ : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
+ let openerWindow = (aFlags & nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener;
+
+ var newTab = gBrowser.loadOneTab(uri, {triggeringPrincipal: aTriggeringPrincipal,
+ referrerURI: referrer,
+ referrerPolicy,
+ inBackground: bgLoad,
+ fromExternal: isExternal,
+ relatedToCurrent: isRelated,
+ userContextId: userContextId,
+ opener: openerWindow,
+ });
+ var contentWin = gBrowser.getBrowserForTab(newTab).contentWindow;
+ if (!bgLoad)
+ contentWin.focus();
+ return contentWin;
+ default:
+ var loadflags = isExternal ?
+ nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
+ nsIWebNavigation.LOAD_FLAGS_NONE;
+
+ if (!aOpener) {
+ if (aURI) {
+ gBrowser.loadURIWithFlags(aURI.spec, {
+ flags: loadflags,
+ referrerURI: referrer,
+ referrerPolicy,
+ triggeringPrincipal: aTriggeringPrincipal,
+ });
+ }
+ return content;
+ }
+ aOpener = aOpener.top;
+ if (aURI) {
+ try {
+ aOpener.QueryInterface(nsIInterfaceRequestor)
+ .getInterface(nsIWebNavigation)
+ .loadURI(uri, loadflags, referrer, null, null,
+ aTriggeringPrincipal);
+ } catch (e) {}
+ }
+ return aOpener;
+ }
+ },
+ isTabContentWindow: function isTabContentWindow(aWindow) {
+ return gBrowser.browsers.some(browser => browser.contentWindow == aWindow);
+ }
+}
+
+function HandleAppCommandEvent(aEvent)
+{
+ aEvent.stopPropagation();
+ switch (aEvent.command) {
+ case "Back":
+ BrowserBack();
+ break;
+ case "Forward":
+ BrowserForward();
+ break;
+ case "Reload":
+ BrowserReloadSkipCache();
+ break;
+ case "Stop":
+ BrowserStop();
+ break;
+ case "Search":
+ BrowserSearch.webSearch();
+ break;
+ case "Bookmarks":
+ toBookmarksManager();
+ break;
+ case "Home":
+ BrowserHome(null);
+ break;
+ default:
+ break;
+ }
+}
+
+/* window.arguments[0]: URL(s) to load
+ (string, with one or more URLs separated by \n)
+ * [1]: character set (string)
+ * [2]: referrer (nsIURI)
+ * [3]: postData (nsIInputStream)
+ * [4]: allowThirdPartyFixup (bool)
+ */
+function Startup()
+{
+ // init globals
+ gNavigatorBundle = document.getElementById("bundle_navigator");
+ gBrandBundle = document.getElementById("bundle_brand");
+ gNavigatorRegionBundle = document.getElementById("bundle_navigator_region");
+
+ gBrowser = document.getElementById("content");
+ gURLBar = document.getElementById("urlbar");
+
+ SetPageProxyState("invalid", null);
+
+ var webNavigation;
+ try {
+ webNavigation = getWebNavigation();
+ if (!webNavigation)
+ throw "no XBL binding for browser";
+ } catch (e) {
+ alert("Error launching browser window:" + e);
+ window.close(); // Give up.
+ return;
+ }
+
+ // Do all UI building here:
+ UpdateNavBar();
+ updateWindowState();
+
+ // set home button tooltip text
+ updateHomeButtonTooltip();
+
+ var lc = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+ if (lc.usePrivateBrowsing) {
+ gPrivate = window;
+ let docElement = document.documentElement;
+ var titlemodifier = docElement.getAttribute("titlemodifier");
+ if (titlemodifier)
+ titlemodifier += " ";
+ titlemodifier += docElement.getAttribute("titleprivate");
+ docElement.setAttribute("titlemodifier", titlemodifier);
+ document.title = titlemodifier;
+ docElement.setAttribute("privatebrowsingmode",
+ PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary");
+ }
+
+ // initialize observers and listeners
+ var xw = lc.QueryInterface(Ci.nsIDocShellTreeItem)
+ .treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow);
+ xw.XULBrowserWindow = window.XULBrowserWindow = new nsBrowserStatusHandler();
+
+ if (!window.content.opener &&
+ Services.prefs.getBoolPref("browser.doorhanger.enabled")) {
+ var tmp = {};
+ ChromeUtils.import("resource://gre/modules/PopupNotifications.jsm", tmp);
+ window.PopupNotifications = new tmp.PopupNotifications(
+ getBrowser(),
+ document.getElementById("notification-popup"),
+ document.getElementById("notification-popup-box"));
+ // Setting the popup notification attribute causes the XBL to bind
+ // and call the constructor again, so we have to destroy it first.
+ gBrowser.getNotificationBox().destroy();
+ gBrowser.setAttribute("popupnotification", "true");
+ // The rebind also resets popup window scrollbar visibility, so override it.
+ if (!(xw.chromeFlags & Ci.nsIWebBrowserChrome.CHROME_SCROLLBARS))
+ gBrowser.selectedBrowser.style.overflow = "hidden";
+ }
+
+ addPrefListener(gTabStripPrefListener);
+ addPrefListener(gHomepagePrefListener);
+ addPrefListener(gStatusBarPopupIconPrefListener);
+ addFormSubmitObserver(gFormSubmitObserver);
+
+ window.browserContentListener =
+ new nsBrowserContentListener(window, getBrowser());
+
+ // Add a capturing event listener to the content area
+ // (rjc note: not the entire window, otherwise we'll get sidebar pane loads too!)
+ // so we'll be notified when onloads complete.
+ var contentArea = document.getElementById("appcontent");
+ contentArea.addEventListener("pageshow", function callPageShowHandlers(aEvent) {
+ // Filter out events that are not about the document load we are interested in.
+ if (aEvent.originalTarget == content.document)
+ setTimeout(pageShowEventHandlers, 0, aEvent);
+ }, true);
+
+ // Set a sane starting width/height for all resolutions on new profiles.
+ if (!document.documentElement.hasAttribute("width")) {
+ var defaultHeight = screen.availHeight;
+ var defaultWidth= screen.availWidth;
+
+ // Create a narrower window for large or wide-aspect displays, to suggest
+ // side-by-side page view.
+ if (screen.availWidth >= 1440)
+ defaultWidth /= 2;
+
+ // Tweak sizes to be sure we don't grow outside the screen
+ defaultWidth = defaultWidth - 20;
+ defaultHeight = defaultHeight - 10;
+
+ // On X, we're not currently able to account for the size of the window
+ // border. Use 28px as a guess (titlebar + bottom window border)
+ if (navigator.appVersion.includes("X11"))
+ defaultHeight -= 28;
+
+ // On small screens, default to maximized state
+ if (defaultHeight <= 600)
+ document.documentElement.setAttribute("sizemode", "maximized");
+
+ document.documentElement.setAttribute("width", defaultWidth);
+ document.documentElement.setAttribute("height", defaultHeight);
+ // Make sure we're safe at the left/top edge of screen
+ document.documentElement.setAttribute("screenX", screen.availLeft);
+ document.documentElement.setAttribute("screenY", screen.availTop);
+ }
+
+ // hook up UI through progress listener
+ getBrowser().addProgressListener(window.XULBrowserWindow);
+ // setup the search service DOMLinkAdded listener
+ getBrowser().addEventListener("DOMLinkAdded", BrowserSearch);
+ // hook up drag'n'drop
+ getBrowser().droppedLinkHandler = handleDroppedLink;
+
+ var uriToLoad = "";
+
+ // Check window.arguments[0]. If not null then use it for uriArray
+ // otherwise the new window is being called when another browser
+ // window already exists so use the New Window pref for uriArray
+ if ("arguments" in window && window.arguments.length >= 1) {
+ var uriArray;
+ if (window.arguments[0]) {
+ uriArray = window.arguments[0].toString().split('\n'); // stringify and split
+ } else {
+ switch (Services.prefs.getIntPref("browser.windows.loadOnNewWindow", 0))
+ {
+ default:
+ uriArray = ["about:blank"];
+ break;
+ case 1:
+ uriArray = getHomePage();
+ break;
+ case 2:
+ uriArray = [Services.prefs.getStringPref("browser.history.last_page_visited", "")];
+ break;
+ }
+ }
+ uriToLoad = uriArray.splice(0, 1)[0];
+
+ if (uriArray.length > 0)
+ window.setTimeout(function(arg) { for (var i in arg) gBrowser.addTab(arg[i]); }, 0, uriArray);
+ }
+
+ if (/^\s*$/.test(uriToLoad))
+ uriToLoad = "about:blank";
+
+ var browser = getBrowser();
+
+ if (uriToLoad != "about:blank") {
+ if (!gInitialPages.has(uriToLoad)) {
+ gURLBar.value = uriToLoad;
+ browser.userTypedValue = uriToLoad;
+ }
+
+ if ("arguments" in window && window.arguments.length >= 3) {
+ // window.arguments[2]: referrer (nsIURI | string)
+ // [3]: postData (nsIInputStream)
+ // [4]: allowThirdPartyFixup (bool)
+ // [5]: referrerPolicy (int)
+ // [6]: userContextId (int)
+ // [7]: originPrincipal (nsIPrincipal)
+ // [8]: triggeringPrincipal (nsIPrincipal)
+ let referrerURI = window.arguments[2];
+ if (typeof(referrerURI) == "string") {
+ try {
+ referrerURI = makeURI(referrerURI);
+ } catch (e) {
+ referrerURI = null;
+ }
+ }
+ let referrerPolicy = (window.arguments[5] != undefined ?
+ window.arguments[5] : Ci.nsIHttpChannel.REFERRER_POLICY_UNSET);
+
+ let userContextId = (window.arguments[6] != undefined ?
+ window.arguments[6] : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID);
+ loadURI(uriToLoad, referrerURI, window.arguments[3] || null,
+ window.arguments[4] || false, referrerPolicy, userContextId,
+ // pass the origin principal (if any) and force its use to create
+ // an initial about:blank viewer if present:
+ window.arguments[7], !!window.arguments[7], window.arguments[8]);
+ } else {
+ // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
+ // Such callers expect that window.arguments[0] is handled as a single URI.
+ loadOneOrMoreURIs(uriToLoad, Services.scriptSecurityManager.getSystemPrincipal());
+ }
+ }
+
+ // Focus content area unless we're loading a blank or other initial
+ // page, or if we weren't passed any arguments. This "breaks" the
+ // javascript:window.open(); case where we don't get any arguments
+ // either, but we're loading about:blank, but focusing the content
+ // area is arguably correct in that case as well since the opener
+ // is very likely to put some content in the new window, and then
+ // the focus should be in the content area.
+ var navBar = document.getElementById("nav-bar");
+ if ("arguments" in window && gInitialPages.has(uriToLoad) &&
+ isElementVisible(gURLBar))
+ setTimeout(WindowFocusTimerCallback, 0, gURLBar);
+ else
+ setTimeout(WindowFocusTimerCallback, 0, content);
+
+ // hook up browser access support
+ window.browserDOMWindow = new nsBrowserAccess();
+
+ // hook up remote support
+ if (!gPrivate && REMOTESERVICE_CONTRACTID in Cc) {
+ var remoteService =
+ Cc[REMOTESERVICE_CONTRACTID]
+ .getService(Ci.nsIRemoteService);
+ remoteService.registerWindow(window);
+ }
+
+ // ensure login manager is loaded
+ Cc["@mozilla.org/login-manager;1"].getService();
+
+ // called when we go into full screen, even if it is
+ // initiated by a web page script
+ addEventListener("fullscreen", onFullScreen, true);
+
+ addEventListener("PopupCountChanged", UpdateStatusBarPopupIcon, true);
+
+ addEventListener("AppCommand", HandleAppCommandEvent, true);
+
+ addEventListener("sizemodechange", updateWindowState, false);
+
+ // does clicking on the urlbar select its contents?
+ gClickSelectsAll = Services.prefs.getBoolPref("browser.urlbar.clickSelectsAll");
+ gClickAtEndSelects = Services.prefs.getBoolPref("browser.urlbar.clickAtEndSelects");
+
+ // BiDi UI
+ gShowBiDi = isBidiEnabled();
+ if (gShowBiDi) {
+ document.getElementById("documentDirection-swap").hidden = false;
+ document.getElementById("textfieldDirection-separator").hidden = false;
+ document.getElementById("textfieldDirection-swap").hidden = false;
+ }
+
+ // Before and after callbacks for the customizeToolbar code
+ getNavToolbox().customizeInit = BrowserToolboxCustomizeInit;
+ getNavToolbox().customizeDone = BrowserToolboxCustomizeDone;
+ getNavToolbox().customizeChange = BrowserToolboxCustomizeChange;
+
+ PlacesToolbarHelper.init();
+
+ gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true);
+ gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true);
+ gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true);
+
+ AeroPeek.onOpenWindow(window);
+
+ if (!gPrivate) {
+ // initialize the sync UI
+ // gSyncUI.init();
+
+ // initialize the session-restore service
+ setTimeout(InitSessionStoreCallback, 0);
+ }
+
+ ZoomListeners.init();
+ gBrowser.addTabsProgressListener(ZoomListeners);
+
+ window.addEventListener("MozAfterPaint", DelayedStartup);
+}
+
+// Minimal gBrowserInit shim to keep the Addon-SDK happy.
+var gBrowserInit = {
+ delayedStartupFinished: false,
+}
+
+function DelayedStartup() {
+ window.removeEventListener("MozAfterPaint", DelayedStartup);
+
+ // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
+ setTimeout(function() { SafeBrowsing.init(); }, 2000);
+
+ gBrowserInit.delayedStartupFinished = true;
+ Services.obs.notifyObservers(window, "browser-delayed-startup-finished");
+}
+
+function UpdateNavBar()
+{
+ var elements = getNavToolbox().getElementsByClassName("nav-bar-class");
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ element.classList.remove("nav-bar-last");
+ element.classList.remove("nav-bar-first");
+ var next = element.nextSibling;
+ if (!next || !next.classList.contains("nav-bar-class"))
+ element.classList.add("nav-bar-last");
+ var previous = element.previousSibling;
+ if (!previous || !previous.classList.contains("nav-bar-class"))
+ element.classList.add("nav-bar-first");
+ }
+ UpdateUrlbarSearchSplitterState();
+}
+
+function UpdateUrlbarSearchSplitterState()
+{
+ var splitter = document.getElementById("urlbar-search-splitter");
+ var urlbar = document.getElementById("nav-bar-inner");
+ var searchbar = document.getElementById("search-container");
+
+ var ibefore = null;
+ if (isElementVisible(urlbar) && isElementVisible(searchbar)) {
+ if (searchbar.matches("#nav-bar-inner ~ #search-container"))
+ ibefore = searchbar;
+ else if (urlbar.matches("#search-container ~ #nav-bar-inner"))
+ ibefore = searchbar.nextSibling;
+ }
+
+ if (ibefore) {
+ splitter = document.createElement("splitter");
+ splitter.id = "urlbar-search-splitter";
+ splitter.setAttribute("resizebefore", "flex");
+ splitter.setAttribute("resizeafter", "flex");
+ splitter.setAttribute("skipintoolbarset", "true");
+ splitter.setAttribute("overflows", "false");
+ splitter.classList.add("chromeclass-toolbar-additional",
+ "nav-bar-class");
+ ibefore.parentNode.insertBefore(splitter, ibefore);
+ }
+}
+
+function updateWindowState()
+{
+ getBrowser().showWindowResizer =
+ window.windowState == window.STATE_NORMAL &&
+ !isElementVisible(document.getElementById("status-bar"));
+ getBrowser().docShellIsActive =
+ window.windowState != window.STATE_MINIMIZED;
+}
+
+function InitSessionStoreCallback()
+{
+ try {
+ var ss = Cc["@mozilla.org/suite/sessionstore;1"]
+ .getService(Ci.nsISessionStore);
+ ss.init(window);
+
+ //Check if we have "Deferred Session Restore"
+ let restoreItem = document.getElementById("historyRestoreLastSession");
+
+ if (ss.canRestoreLastSession)
+ restoreItem.removeAttribute("disabled");
+ } catch(ex) {
+ dump("nsSessionStore could not be initialized: " + ex + "\n");
+ }
+}
+
+function WindowFocusTimerCallback(element)
+{
+ // This function is a redo of the fix for jag bug 91884.
+ // See Bug 97067 and Bug 89214 for details.
+ if (window == Services.ww.activeWindow) {
+ element.focus();
+ } else {
+ // set the element in command dispatcher so focus will restore properly
+ // when the window does become active
+
+ if (element instanceof Ci.nsIDOMWindow) {
+ document.commandDispatcher.focusedWindow = element;
+ document.commandDispatcher.focusedElement = null;
+ } else if (Element.isInstance(element)) {
+ document.commandDispatcher.focusedWindow = element.ownerDocument.defaultView;
+ document.commandDispatcher.focusedElement = element;
+ }
+ }
+}
+
+function Shutdown()
+{
+ AeroPeek.onCloseWindow(window);
+
+ BookmarkingUI.uninit();
+
+ // shut down browser access support
+ window.browserDOMWindow = null;
+
+ getBrowser().removeEventListener("DOMLinkAdded", BrowserSearch);
+
+ try {
+ getBrowser().removeProgressListener(window.XULBrowserWindow);
+ } catch (ex) {
+ // Perhaps we didn't get around to adding the progress listener
+ }
+
+ window.XULBrowserWindow.destroy();
+ window.XULBrowserWindow = null;
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow)
+ .XULBrowserWindow = null;
+
+ // unregister us as a pref listener
+ removePrefListener(gTabStripPrefListener);
+ removePrefListener(gHomepagePrefListener);
+ removePrefListener(gStatusBarPopupIconPrefListener);
+ removeFormSubmitObserver(gFormSubmitObserver);
+
+ window.browserContentListener.close();
+}
+
+function Translate()
+{
+ var service = GetLocalizedStringPref("browser.translation.service");
+ var serviceDomain = GetLocalizedStringPref("browser.translation.serviceDomain");
+ var targetURI = getWebNavigation().currentURI.spec;
+
+ // if we're already viewing a translated page, then just reload
+ if (targetURI.includes(serviceDomain))
+ BrowserReload();
+ else {
+ loadURI(encodeURI(service) + encodeURIComponent(targetURI));
+ }
+}
+
+function GetTypePermFromId(aId)
+{
+ // Get type and action from splitting id, first is type, second is action.
+ var [type, action] = aId.split("_");
+ var perm = "ACCESS_" + action.toUpperCase();
+ return [type, Ci.nsICookiePermission[perm]];
+}
+
+// Determine current state and check/uncheck the appropriate menu items.
+function CheckPermissionsMenu(aType, aNode)
+{
+ var currentPerm = Services.perms.testPermission(getBrowser().currentURI, aType);
+ var items = aNode.getElementsByAttribute("name", aType);
+ for (let item of items) {
+ // Get type and perm from id.
+ var [type, perm] = GetTypePermFromId(item.id);
+ item.setAttribute("checked", perm == currentPerm);
+ }
+}
+
+// Perform a Cookie, Image or Popup action.
+function CookieImagePopupAction(aElement)
+{
+ var uri = getBrowser().currentURI;
+ // Get type and perm from id.
+ var [type, perm] = GetTypePermFromId(aElement.id);
+ if (Services.perms.testPermission(uri, type) == perm)
+ return;
+
+ Services.perms.add(uri, type, perm);
+
+ Services.prompt.alert(window, aElement.getAttribute("title"),
+ aElement.getAttribute("msg"));
+}
+function OpenSessionHistoryIn(aWhere, aDelta, aTab)
+{
+ var win = aWhere == "window" ? null : window;
+ aTab = aTab || getBrowser().selectedTab;
+ var tab = Cc["@mozilla.org/suite/sessionstore;1"]
+ .getService(Ci.nsISessionStore)
+ .duplicateTab(win, aTab, aDelta, true);
+
+ var loadInBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+
+ switch (aWhere) {
+ case "tabfocused":
+ // forces tab to be focused
+ loadInBackground = true;
+ // fall through
+ case "tabshifted":
+ loadInBackground = !loadInBackground;
+ // fall through
+ case "tab":
+ if (!loadInBackground) {
+ getBrowser().selectedTab = tab;
+ window.content.focus();
+ }
+ }
+}
+
+/* Firefox compatibility shim *
+ * duplicateTabIn duplicates tab in a place specified by the parameter |where|.
+ *
+ * |where| can be:
+ * "tab" new tab
+ * "tabshifted" same as "tab" but in background if default is to select new
+ * tabs, and vice versa
+ * "tabfocused" same as "tab" but override any background preferences and
+ * focus the new tab
+ * "window" new window
+ *
+ * delta is the offset to the history entry that you want to load.
+ */
+function duplicateTabIn(aTab, aWhere, aDelta)
+{
+ OpenSessionHistoryIn(aWhere, aDelta, aTab);
+}
+
+function gotoHistoryIndex(aEvent)
+{
+ var index = aEvent.target.getAttribute("index");
+ if (!index)
+ return false;
+
+ var where = whereToOpenLink(aEvent);
+ if (where == "current") {
+ // Normal click. Go there in the current tab and update session history.
+ try {
+ getWebNavigation().gotoIndex(index);
+ }
+ catch(ex) {
+ return false;
+ }
+ }
+ else {
+ // Modified click. Go there in a new tab/window. Include session history.
+ var delta = index - getWebNavigation().sessionHistory.index;
+ OpenSessionHistoryIn(where, delta);
+ }
+ return true;
+}
+
+function BrowserBack(aEvent)
+{
+ var where = whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
+ try {
+ getBrowser().goBack();
+ }
+ catch(ex) {}
+ }
+ else {
+ OpenSessionHistoryIn(where, -1);
+ }
+}
+
+function BrowserHandleBackspace()
+{
+ switch (Services.prefs.getIntPref("browser.backspace_action")) {
+ case 0:
+ BrowserBack();
+ break;
+ case 1:
+ goDoCommand("cmd_scrollPageUp");
+ break;
+ }
+}
+
+function BrowserForward(aEvent)
+{
+ var where = whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
+ try {
+ getBrowser().goForward();
+ }
+ catch(ex) {
+ }
+ }
+ else {
+ OpenSessionHistoryIn(where, 1);
+ }
+}
+
+function BrowserUp()
+{
+ loadURI(getBrowser().currentURI.spec.replace(/[#?].*$/, "").replace(/\/[^\/]*.$/, "/"));
+}
+
+function BrowserHandleShiftBackspace()
+{
+ switch (Services.prefs.getIntPref("browser.backspace_action")) {
+ case 0:
+ BrowserForward();
+ break;
+ case 1:
+ goDoCommand("cmd_scrollPageDown");
+ break;
+ }
+}
+
+function BrowserBackMenu(event)
+{
+ return FillHistoryMenu(event.target, "back");
+}
+
+function BrowserForwardMenu(event)
+{
+ return FillHistoryMenu(event.target, "forward");
+}
+
+function BrowserStop()
+{
+ try {
+ const stopFlags = nsIWebNavigation.STOP_ALL;
+ getWebNavigation().stop(stopFlags);
+ }
+ catch(ex) {
+ }
+}
+
+function BrowserReload(aEvent)
+{
+ var where = whereToOpenLink(aEvent, false, true);
+ if (where == "current")
+ BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_NONE);
+ else if (where == null && aEvent.shiftKey)
+ BrowserReloadSkipCache();
+ else
+ OpenSessionHistoryIn(where, 0);
+}
+
+function BrowserReloadSkipCache()
+{
+ // Bypass proxy and cache.
+ const reloadFlags = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
+ BrowserReloadWithFlags(reloadFlags);
+}
+
+function BrowserHome(aEvent)
+{
+ var homePage = getHomePage();
+ var where = whereToOpenLink(aEvent, false, true);
+ openUILinkArrayIn(homePage, where);
+}
+
+var BrowserSearch = {
+ handleEvent: function (event) { // "DOMLinkAdded" event
+ var link = event.originalTarget;
+
+ var isSearch = /(?:^|\s)search(?:\s|$)/i.test(link.rel) && link.title &&
+ /^(https?|ftp):/i.test(link.href) &&
+ /(?:^|\s)application\/opensearchdescription\+xml(?:;?.*)$/i.test(link.type);
+
+ if (isSearch) {
+ this.addEngine(link, link.ownerDocument);
+ }
+ },
+
+ addEngine: function(engine, targetDoc) {
+ if (!this.searchBar)
+ return;
+
+ var browser = getBrowser().getBrowserForDocument(targetDoc);
+ // ignore search engines from subframes (see bug 479408)
+ if (!browser)
+ return;
+
+ // Check to see whether we've already added an engine with this title
+ if (browser.engines) {
+ if (browser.engines.some(e => e.title == engine.title))
+ return;
+ }
+
+ // Append the URI and an appropriate title to the browser data.
+ // Use documentURIObject in the check so that we do the right
+ // thing with about:-style error pages. Bug 453442
+ var iconURL = null;
+ var aURI = targetDoc.documentURIObject;
+ try {
+ aURI = Services.uriFixup.createExposableURI(aURI);
+ } catch (e) {
+ }
+
+ if (aURI && ("schemeIs" in aURI) &&
+ (aURI.schemeIs("http") || aURI.schemeIs("https")))
+ iconURL = getBrowser().buildFavIconString(aURI);
+
+ var hidden = false;
+ // If this engine (identified by title) is already in the list, add it
+ // to the list of hidden engines rather than to the main list.
+ // XXX This will need to be changed when engines are identified by URL;
+ // see bug 335102.
+ if (Services.search.getEngineByName(engine.title))
+ hidden = true;
+
+ var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];
+
+ engines.push({ uri: engine.href,
+ title: engine.title,
+ icon: iconURL });
+
+ if (hidden)
+ browser.hiddenEngines = engines;
+ else {
+ browser.engines = engines;
+ if (browser == getBrowser().selectedBrowser)
+ this.updateSearchButton();
+ }
+ },
+
+ /**
+ * Update the browser UI to show whether or not additional engines are
+ * available when a page is loaded or the user switches tabs to a page that
+ * has search engines.
+ */
+ updateSearchButton: function() {
+ var searchBar = this.searchBar;
+
+ // The search bar binding might not be applied even though the element is
+ // in the document (e.g. when the navigation toolbar is hidden), so check
+ // for .searchButton specifically.
+ if (!searchBar || !searchBar.searchButton)
+ return;
+
+ searchBar.updateSearchButton();
+ },
+
+ /**
+ * Gives focus to the search bar, if it is present on the toolbar, or to the
+ * search sidebar, if it is open, or loads the default engine's search form
+ * otherwise. For Mac, opens a new window or focuses an existing window, if
+ * necessary.
+ */
+ webSearch: function BrowserSearch_webSearch() {
+ if (!gBrowser) {
+ var win = getTopWin();
+ if (win) {
+ // If there's an open browser window, it should handle this command
+ win.focus();
+ win.BrowserSearch.webSearch();
+ return;
+ }
+
+ // If there are no open browser windows, open a new one
+ function webSearchCallback() {
+ // This needs to be in a timeout so that we don't end up refocused
+ // in the url bar
+ setTimeout(BrowserSearch.webSearch, 0);
+ }
+
+ win = window.openDialog(getBrowserURL(), "_blank",
+ "chrome,all,dialog=no", "about:blank");
+ win.addEventListener("load", webSearchCallback);
+ return;
+ }
+
+ if (isElementVisible(this.searchBar)) {
+ this.searchBar.select();
+ this.searchBar.focus();
+ } else if (this.searchSidebar) {
+ this.searchSidebar.focus();
+ } else {
+ loadURI(Services.search.defaultEngine.searchForm);
+ window.content.focus();
+ }
+ },
+
+ /**
+ * Loads a search results page, given a set of search terms. Uses the current
+ * engine if the search bar is visible, or the default engine otherwise.
+ *
+ * @param aSearchText
+ * The search terms to use for the search.
+ *
+ * @param [optional] aNewWindowOrTab
+ * A boolean if set causes the search to load in a new window or tab
+ * (depending on "browser.search.openintab"). Otherwise the search
+ * loads in the current tab.
+ *
+ * @param [optional] aEvent
+ * The event object passed from the caller.
+ */
+ loadSearch: function BrowserSearch_search(aSearchText, aNewWindowOrTab, aEvent) {
+ var engine;
+
+ // If the search bar is visible, use the current engine, otherwise, fall
+ // back to the default engine.
+ if (isElementVisible(this.searchBar) ||
+ this.searchSidebar)
+ engine = Services.search.currentEngine;
+ else
+ engine = Services.search.defaultEngine;
+
+ var submission = engine.getSubmission(aSearchText); // HTML response
+
+ // getSubmission can return null if the engine doesn't have a URL
+ // with a text/html response type. This is unlikely (since
+ // SearchService._addEngineToStore() should fail for such an engine),
+ // but let's be on the safe side.
+ // If you change the code here, remember to make the corresponding
+ // changes in suite/mailnews/mailWindowOverlay.js->MsgOpenSearch
+ if (!submission)
+ return;
+
+ if (aNewWindowOrTab) {
+ let newTabPref = Services.prefs.getBoolPref("browser.search.opentabforcontextsearch");
+ let where = newTabPref ? aEvent && aEvent.shiftKey ? "tabshifted" : "tab" : "window";
+ openUILinkIn(submission.uri.spec, where, null, submission.postData);
+ if (where == "window")
+ return;
+ } else {
+ loadURI(submission.uri.spec, null, submission.postData, false);
+ window.content.focus();
+ }
+ },
+
+ /**
+ * Returns the search bar element if it is present in the toolbar, null otherwise.
+ */
+ get searchBar() {
+ return document.getElementById("searchbar");
+ },
+
+ /**
+ * Returns the search sidebar textbox if the search sidebar is present in
+ * the sidebar and selected, null otherwise.
+ */
+ get searchSidebar() {
+ if (sidebarObj.never_built)
+ return null;
+ var panel = sidebarObj.panels.get_panel_from_id("urn:sidebar:panel:search");
+ return panel && isElementVisible(panel.get_iframe()) &&
+ panel.get_iframe()
+ .contentDocument.getElementById("sidebar-search-text");
+ },
+
+ loadAddEngines: function BrowserSearch_loadAddEngines() {
+ loadAddSearchEngines(); // for compatibility
+ },
+
+ /**
+ * Reveal the search sidebar panel.
+ */
+ revealSidebar: function BrowserSearch_revealSidebar() {
+ // first lets check if the search panel will be shown at all
+ // by checking the sidebar datasource to see if there is an entry
+ // for the search panel, and if it is excluded for navigator or not
+
+ var searchPanelExists = false;
+
+ var myPanel = document.getElementById("urn:sidebar:panel:search");
+ if (myPanel) {
+ var panel = sidebarObj.panels.get_panel_from_header_node(myPanel);
+ searchPanelExists = !panel.is_excluded();
+
+ } else if (sidebarObj.never_built) {
+ // XXXsearch: in theory, this should work when the sidebar isn't loaded,
+ // in practice, it fails as sidebarObj.datasource_uri isn't defined
+ try {
+ var datasource = RDF.GetDataSourceBlocking(sidebarObj.datasource_uri);
+ var aboutValue = RDF.GetResource("urn:sidebar:panel:search");
+
+ // check if the panel is even in the list by checking for its content
+ var contentProp = RDF.GetResource("http://home.netscape.com/NC-rdf#content");
+ var content = datasource.GetTarget(aboutValue, contentProp, true);
+
+ if (content instanceof Ci.nsIRDFLiteral) {
+ // the search panel entry exists, now check if it is excluded
+ // for navigator
+ var excludeProp = RDF.GetResource("http://home.netscape.com/NC-rdf#exclude");
+ var exclude = datasource.GetTarget(aboutValue, excludeProp, true);
+
+ if (exclude instanceof Ci.nsIRDFLiteral) {
+ searchPanelExists = !exclude.Value.includes("navigator:browser");
+ } else {
+ // panel exists and no exclude set
+ searchPanelExists = true;
+ }
+ }
+ } catch (e) {
+ searchPanelExists = false;
+ }
+ }
+
+ if (searchPanelExists) {
+ // make sure the sidebar is open, else SidebarSelectPanel() will fail
+ if (sidebar_is_hidden())
+ SidebarShowHide();
+
+ if (sidebar_is_collapsed())
+ SidebarExpandCollapse();
+
+ var searchPanel = document.getElementById("urn:sidebar:panel:search");
+ if (searchPanel)
+ SidebarSelectPanel(searchPanel, true, true); // lives in sidebarOverlay.js
+ }
+ }
+}
+
+function QualifySearchTerm()
+{
+ // If the text in the URL bar is the same as the currently loaded
+ // page's URL then treat this as an empty search term. This way
+ // the user is taken to the search page where s/he can enter a term.
+ if (gBrowser.userTypedValue !== null)
+ return gURLBar.value;
+ return "";
+}
+
+function BrowserOpenWindow()
+{
+ //opens a window where users can select a web location to open
+ var params = { action: gPrivate ? "4" : "0", url: "" };
+ openDialog("chrome://communicator/content/openLocation.xul", "_blank",
+ "chrome,modal,titlebar", params);
+
+ getShortcutOrURIAndPostData(params.url).then(data => {
+ switch (params.action) {
+ case "0": // current window
+ loadURI(data.url, null, data.postData, true);
+ break;
+ case "1": // new window
+ openDialog(getBrowserURL(), "_blank", "all,dialog=no", data.url, null, null,
+ data.postData, true);
+ break;
+ case "2": // edit
+ editPage(data.url);
+ break;
+ case "3": // new tab
+ gBrowser.selectedTab = gBrowser.addTab(data.url,
+ {allowThirdPartyFixup: true,
+ postData: data.postData});
+ break;
+ case "4": // private
+ openNewPrivateWith(params.url);
+ break;
+ }
+ });
+}
+
+function BrowserOpenTab()
+{
+ if (!gInPrintPreviewMode) {
+ var uriToLoad;
+ var tabPref = Services.prefs.getIntPref("browser.tabs.loadOnNewTab", 0);
+ switch (tabPref)
+ {
+ default:
+ uriToLoad = "about:blank";
+ break;
+ case 1:
+ uriToLoad = GetLocalizedStringPref("browser.startup.homepage");
+ break;
+ case 2:
+ uriToLoad = Services.prefs.getStringPref("browser.history.last_page_visited", "");
+ break;
+ }
+
+ if (!gBrowser) {
+ var win = getTopWin();
+ if (win) {
+ // If there's an open browser window, it should handle this command
+ win.focus();
+ win.BrowserOpenTab();
+ return;
+ }
+
+ // If there are no open browser windows, open a new one
+ openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", uriToLoad);
+ return;
+ }
+
+ if (tabPref == 2)
+ OpenSessionHistoryIn("tabfocused", 0);
+ else
+ gBrowser.selectedTab = gBrowser.addTab(uriToLoad);
+
+ if (uriToLoad == "about:blank" && isElementVisible(gURLBar))
+ setTimeout(WindowFocusTimerCallback, 0, gURLBar);
+ else
+ setTimeout(WindowFocusTimerCallback, 0, content);
+ }
+}
+
+function BrowserOpenSyncTabs()
+{
+ switchToTabHavingURI("about:sync-tabs", true);
+}
+// Class for saving the last directory and filter Index in the prefs.
+// Used for open file and upload file.
+class RememberLastDir {
+
+ // The pref names are constructed from the prefix parameter in the constructor.
+ // The pref names should not be changed later.
+ constructor(prefPrefix) {
+ this._prefLastDir = prefPrefix + ".lastDir";
+ this._prefFilterIndex = prefPrefix + ".filterIndex";
+ this._lastDir = null;
+ this._lastFilterIndex = null;
+ }
+
+ get path() {
+ if (!this._lastDir || !this._lastDir.exists()) {
+ try {
+ this._lastDir = Services.prefs.getComplexValue(this._prefLastDir,
+ Ci.nsIFile);
+ if (!this._lastDir.exists()) {
+ this._lastDir = null;
+ }
+ } catch (e) {}
+ }
+ return this._lastDir;
+ }
+
+ set path(val) {
+ try {
+ if (!val || !val.isDirectory()) {
+ return;
+ }
+ } catch (e) {
+ return;
+ }
+ this._lastDir = val.clone();
+
+ // Don't save the last open directory pref inside the Private Browsing mode
+ if (!gPrivate) {
+ Services.prefs.setComplexValue(this._prefLastDir,
+ Ci.nsIFile,
+ this._lastDir);
+ }
+ }
+
+ get filterIndex() {
+ if (!this._lastFilterIndex) {
+ // use a pref to remember the filterIndex selected by the user.
+ this._lastFilterIndex =
+ Services.prefs.getIntPref(this._prefFilterIndex, 0);
+ }
+ return this._lastFilterIndex;
+ }
+
+ set filterIndex(val) {
+ // If the default is picked the filter is null.
+ this._lastFilterIndex = val ? val : 0;
+
+ // Don't save the last filter index inside the Private Browsing mode
+ if (!gPrivate) {
+ Services.prefs.setIntPref(this._prefFilterIndex,
+ this._lastFilterIndex);
+ }
+ }
+
+ // This is currently not used.
+ reset() {
+ this._lastDir = null;
+ this._lastFilterIndex = null;
+ }
+}
+
+var gLastOpenDirectory;
+
+function BrowserOpenFileWindow() {
+
+ if (!gLastOpenDirectory) {
+ gLastOpenDirectory = new RememberLastDir("browser.open");
+ };
+
+ // Get filepicker component.
+ try {
+ const nsIFilePicker = Ci.nsIFilePicker;
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK) {
+ try {
+ // Set last path and file index only if file is ok.
+ if (fp.file) {
+ gLastOpenDirectory.filterIndex = fp.filterIndex;
+ gLastOpenDirectory.path =
+ fp.file.parent.QueryInterface(Ci.nsIFile);
+ }
+ } catch (ex) {
+ }
+ openUILinkIn(fp.fileURL.spec, "current");
+ }
+ };
+
+ fp.init(window, gNavigatorBundle.getString("openFile"),
+ nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText |
+ nsIFilePicker.filterImages | nsIFilePicker.filterXML |
+ nsIFilePicker.filterHTML);
+ fp.filterIndex = gLastOpenDirectory.filterIndex;
+ fp.displayDirectory = gLastOpenDirectory.path;
+ fp.open(fpCallback);
+ } catch (ex) {
+ }
+}
+
+function updateCloseItems()
+{
+ var browser = getBrowser();
+
+ var hideCloseWindow = Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab") &&
+ (!browser || browser.tabContainer.childNodes.length <= 1);
+ document.getElementById("menu_closeWindow").hidden = hideCloseWindow;
+ var closeItem = document.getElementById("menu_close");
+ if (hideCloseWindow) {
+ closeItem.setAttribute("label", gNavigatorBundle.getString("tabs.close.label"));
+ closeItem.setAttribute("accesskey", gNavigatorBundle.getString("tabs.close.accesskey"));
+ } else {
+ closeItem.setAttribute("label", gNavigatorBundle.getString("tabs.closeTab.label"));
+ closeItem.setAttribute("accesskey", gNavigatorBundle.getString("tabs.closeTab.accesskey"));
+ }
+
+ var hideClose = !browser || !browser.getStripVisibility();
+ document.getElementById("menu_closeOtherTabs").hidden = hideClose;
+ if (!hideClose)
+ document.getElementById("cmd_closeOtherTabs").disabled = hideCloseWindow;
+
+ hideClose = !browser ||
+ (browser.getTabsToTheEndFrom(browser.mCurrentTab).length == 0);
+ document.getElementById("menu_closeTabsToTheEnd").hidden = hideClose;
+ if (!hideClose)
+ document.getElementById("cmd_closeTabsToTheEnd").disabled = hideClose;
+}
+
+function updateRecentMenuItems()
+{
+ var browser = getBrowser();
+ var ss = Cc["@mozilla.org/suite/sessionstore;1"]
+ .getService(Ci.nsISessionStore);
+
+ var recentTabsItem = document.getElementById("menu_recentTabs");
+ recentTabsItem.setAttribute("disabled", !browser || browser.getUndoList().length == 0);
+ var recentWindowsItem = document.getElementById("menu_recentWindows");
+ recentWindowsItem.setAttribute("disabled", ss.getClosedWindowCount() == 0);
+}
+
+function updateRecentTabs(menupopup)
+{
+ var browser = getBrowser();
+
+ while (menupopup.hasChildNodes())
+ menupopup.lastChild.remove();
+
+ var list = browser.getUndoList();
+ for (var i = 0; i < list.length; i++) {
+ var menuitem = document.createElement("menuitem");
+ var label = list[i];
+ if (i < 9) {
+ label = gNavigatorBundle.getFormattedString("tabs.recentlyClosed.format", [i + 1, label]);
+ menuitem.setAttribute("accesskey", i + 1);
+ }
+
+ if (i == 0)
+ menuitem.setAttribute("key", "key_restoreTab");
+
+ menuitem.setAttribute("label", label);
+ menuitem.setAttribute("value", i);
+ menupopup.appendChild(menuitem);
+ }
+}
+
+function updateRecentWindows(menupopup)
+{
+ var ss = Cc["@mozilla.org/suite/sessionstore;1"]
+ .getService(Ci.nsISessionStore);
+
+ while (menupopup.hasChildNodes())
+ menupopup.lastChild.remove();
+
+ var undoItems = JSON.parse(ss.getClosedWindowData());
+ for (var i = 0; i < undoItems.length; i++) {
+ var menuitem = document.createElement("menuitem");
+ var label = undoItems[i].title;
+ if (i < 9) {
+ label = gNavigatorBundle.getFormattedString("windows.recentlyClosed.format", [i + 1, label]);
+ menuitem.setAttribute("accesskey", i + 1);
+ }
+
+ if (i == 0)
+ menuitem.setAttribute("key", "key_restoreWindow");
+
+ menuitem.setAttribute("label", label);
+ menuitem.setAttribute("value", i);
+ menupopup.appendChild(menuitem);
+ }
+}
+
+function undoCloseWindow(aIndex)
+{
+ var ss = Cc["@mozilla.org/suite/sessionstore;1"]
+ .getService(Ci.nsISessionStore);
+
+ return ss.undoCloseWindow(aIndex);
+}
+
+function restoreLastSession() {
+ let ss = Cc["@mozilla.org/suite/sessionstore;1"]
+ .getService(Ci.nsISessionStore);
+ ss.restoreLastSession();
+}
+
+/*
+ * Determines if a tab is "empty" using isBrowserEmpty from utilityOverlay.js
+ */
+function isTabEmpty(aTab)
+{
+ return isBrowserEmpty(aTab.linkedBrowser);
+}
+
+function BrowserCloseOtherTabs()
+{
+ var browser = getBrowser();
+ browser.removeAllTabsBut(browser.mCurrentTab);
+}
+
+function BrowserCloseTabsToTheEnd()
+{
+ var browser = getBrowser();
+ browser.removeTabsToTheEndFrom(browser.mCurrentTab);
+}
+
+function BrowserCloseTabOrWindow()
+{
+ var browser = getBrowser();
+ if (browser.tabContainer.childNodes.length > 1 ||
+ !Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab")) {
+ // Just close up a tab.
+ browser.removeCurrentTab();
+ return;
+ }
+
+ BrowserCloseWindow();
+}
+
+function BrowserTryToCloseWindow()
+{
+ if (WindowIsClosing())
+ BrowserCloseWindow();
+}
+
+function BrowserCloseWindow()
+{
+ // This code replicates stuff in Shutdown(). It is here because
+ // window.screenX and window.screenY have real values. We need
+ // to fix this eventually but by replicating the code here, we
+ // provide a means of saving position (it just requires that the
+ // user close the window via File->Close (vs. close box).
+
+ // Get the current window position/size.
+ var x = window.screenX;
+ var y = window.screenY;
+ var h = window.outerHeight;
+ var w = window.outerWidth;
+
+ // Store these into the window attributes (for persistence).
+ var win = document.getElementById( "main-window" );
+ win.setAttribute( "x", x );
+ win.setAttribute( "y", y );
+ win.setAttribute( "height", h );
+ win.setAttribute( "width", w );
+
+ window.close();
+}
+
+function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy,
+ userContextId, originPrincipal,
+ forceAboutBlankViewerInCurrent, triggeringPrincipal) {
+ try {
+ openLinkIn(uri, "current",
+ { referrerURI: referrer,
+ referrerPolicy: referrerPolicy,
+ postData: postData,
+ allowThirdPartyFixup: allowThirdPartyFixup,
+ userContextId: userContextId,
+ originPrincipal,
+ triggeringPrincipal,
+ forceAboutBlankViewerInCurrent, });
+ } catch (e) {}
+}
+
+function loadOneOrMoreURIs(aURIString, aTriggeringPrincipal) {
+ // we're not a browser window, pass the URI string to a new browser window
+ if (window.location.href != getBrowserURL()) {
+ window.openDialog(getBrowserURL(), "_blank", "all,dialog=no", aURIString);
+ return;
+ }
+
+ // This function throws for certain malformed URIs, so use exception handling
+ // so that we don't disrupt startup
+ try {
+ gBrowser.loadTabs(aURIString.split("|"), {
+ inBackground: false,
+ replace: true,
+ triggeringPrincipal: aTriggeringPrincipal,
+ });
+ } catch (e) {
+ }
+}
+
+function handleURLBarCommand(aUserAction, aTriggeringEvent)
+{
+ // Remove leading and trailing spaces first
+ var url = gURLBar.value.trim();
+ try {
+ addToUrlbarHistory(url);
+ } catch (ex) {
+ // Things may go wrong when adding url to the location bar history,
+ // but don't let that interfere with the loading of the url.
+ }
+
+ if (url.match(/^view-source:/)) {
+ gViewSourceUtils.viewSource(url.replace(/^view-source:/, ""), null, null);
+ return;
+ }
+
+ getShortcutOrURIAndPostData(url).then(data => {
+ // Check the pressed modifiers: (also see bug 97123)
+ // Modifier Mac | Modifier PC | Action
+ // -------------+-------------+-----------
+ // Command | Control | New Window/Tab
+ // Shift+Cmd | Shift+Ctrl | New Window/Tab behind current one
+ // Option | Shift | Save URL (show Filepicker)
+
+ // If false, the save modifier is Alt, which is Option on Mac.
+ var modifierIsShift = Services.prefs.getBoolPref("ui.key.saveLink.shift", true);
+
+ var shiftPressed = false;
+ var saveModifier = false; // if the save modifier was pressed
+ if (aTriggeringEvent && 'shiftKey' in aTriggeringEvent &&
+ 'altKey' in aTriggeringEvent) {
+ saveModifier = modifierIsShift ? aTriggeringEvent.shiftKey
+ : aTriggeringEvent.altKey;
+ shiftPressed = aTriggeringEvent.shiftKey;
+ }
+
+ var browser = getBrowser();
+ // Accept both Control and Meta (=Command) as New-Window-Modifiers
+ if (aTriggeringEvent &&
+ (('ctrlKey' in aTriggeringEvent && aTriggeringEvent.ctrlKey) ||
+ ('metaKey' in aTriggeringEvent && aTriggeringEvent.metaKey) ||
+ ('button' in aTriggeringEvent && aTriggeringEvent.button == 1))) {
+ // Check if user requests Tabs instead of windows
+ if (Services.prefs.getBoolPref("browser.tabs.opentabfor.urlbar", false)) {
+ // Reset url in the urlbar
+ URLBarSetURI();
+ // Open link in new tab
+ var t = browser.addTab(data.url, {
+ postData: data.postData,
+ allowThirdPartyFixup: true,
+ });
+
+ // Focus new tab unless shift is pressed
+ if (!shiftPressed)
+ browser.selectedTab = t;
+ } else {
+ // Open a new window with the URL
+ var newWin = openDialog(getBrowserURL(), "_blank", "all,dialog=no", data.url,
+ null, null, data.postData, true);
+ // Reset url in the urlbar
+ URLBarSetURI();
+
+ // Focus old window if shift was pressed, as there's no
+ // way to open a new window in the background
+ // XXX this doesn't seem to work
+ if (shiftPressed) {
+ //newWin.blur();
+ content.focus();
+ }
+ }
+ } else if (saveModifier) {
+ try {
+ // Firstly, fixup the url so that (e.g.) "www.foo.com" works
+ url = Services.uriFixup.createFixupURI(data.url, Ci.nsIURIFixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI).spec;
+ // Open filepicker to save the url
+ saveURL(url, null, null, false, true, null, document);
+ }
+ catch(ex) {
+ // XXX Do nothing for now.
+ // Do we want to put up an alert in the future? Mmm, l10n...
+ }
+ } else {
+ // No modifier was pressed, load the URL normally and
+ // focus the content area
+ loadURI(data.url, null, data.postData, true);
+ content.focus();
+ }
+ });
+}
+
+/**
+ * Given a string, will generate a more appropriate urlbar value if a Places
+ * keyword or a search alias is found at the beginning of it.
+ *
+ * @param url
+ * A string that may begin with a keyword or an alias.
+ *
+ * @return {Promise}
+ * @resolves { url, postData, mayInheritPrincipal }. If it's not possible
+ * to discern a keyword or an alias, url will be the input string.
+ */
+function getShortcutOrURIAndPostData(url) {
+ return (async function() {
+ let mayInheritPrincipal = false;
+ let postData = null;
+ // Split on the first whitespace.
+ let [keyword, param = ""] = url.trim().split(/\s(.+)/, 2);
+
+ if (!keyword) {
+ return { url, postData, mayInheritPrincipal };
+ }
+
+ let engine = Services.search.getEngineByAlias(keyword);
+ if (engine) {
+ let submission = engine.getSubmission(param, null, "keyword");
+ return { url: submission.uri.spec,
+ postData: submission.postData,
+ mayInheritPrincipal };
+ }
+
+ // A corrupt Places database could make this throw, breaking navigation
+ // from the location bar.
+ let entry = null;
+ try {
+ entry = await PlacesUtils.keywords.fetch(keyword);
+ } catch (ex) {
+ Cu.reportError(`Unable to fetch Places keyword "${keyword}": ${ex}`);
+ }
+ if (!entry || !entry.url) {
+ // This is not a Places keyword.
+ return { url, postData, mayInheritPrincipal };
+ }
+
+ try {
+ [url, postData] =
+ await BrowserUtils.parseUrlAndPostData(entry.url.href,
+ entry.postData,
+ param);
+ if (postData) {
+ postData = getPostDataStream(postData);
+ }
+
+ // Since this URL came from a bookmark, it's safe to let it inherit the
+ // current document's principal.
+ mayInheritPrincipal = true;
+ } catch (ex) {
+ // It was not possible to bind the param, just use the original url value.
+ }
+
+ return { url, postData, mayInheritPrincipal };
+ })().then(data => {
+ return data;
+ });
+}
+
+function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType)
+{
+ var dataStream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword);
+ dataStream.data = aStringData;
+
+ var mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"]
+ .createInstance(Ci.nsIMIMEInputStream);
+ mimeStream.addHeader("Content-Type", aType);
+ mimeStream.setData(dataStream);
+ return mimeStream.QueryInterface(Ci.nsIInputStream);
+}
+
+// handleDroppedLink has the following 2 overloads:
+// handleDroppedLink(event, url, name, triggeringPrincipal)
+// handleDroppedLink(event, links, triggeringPrincipal)
+function handleDroppedLink(event, urlOrLinks, nameOrTriggeringPrincipal, triggeringPrincipal)
+{
+ let links;
+ if (Array.isArray(urlOrLinks)) {
+ links = urlOrLinks;
+ triggeringPrincipal = nameOrTriggeringPrincipal;
+ } else {
+ links = [{ url: urlOrLinks, nameOrTriggeringPrincipal, type: "" }];
+ }
+
+ let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
+
+ // Usually blank for SeaMonkey.
+ let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
+
+ // event is null if links are dropped in content process.
+ // inBackground should be false, as it's loading into current browser.
+ let inBackground = false;
+ if (event) {
+ inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+ if (event.shiftKey)
+ inBackground = !inBackground;
+ }
+
+ (async function() {
+ let urls = [];
+ let postDatas = [];
+ for (let link of links) {
+ let data = await getShortcutOrURIAndPostData(link.url);
+ urls.push(data.url);
+ postDatas.push(data.postData);
+ }
+ if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
+ gBrowser.loadTabs(urls, {
+ inBackground,
+ replace: true,
+ allowThirdPartyFixup: false,
+ postDatas,
+ userContextId,
+ triggeringPrincipal,
+ });
+ }
+ })();
+
+ // If links are dropped in content process, event.preventDefault() should be
+ // called in content process.
+ if (event) {
+ // Keep the event from being handled by the dragDrop listeners
+ // built-in to gecko if they happen to be above us.
+ event.preventDefault();
+ }
+}
+
+function readFromClipboard()
+{
+ var url;
+
+ try {
+ // Get the clipboard.
+ const clipboard = Services.clipboard;
+
+ // Create a transferable that will transfer the text.
+ var trans = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+
+ trans.init(null);
+ trans.addDataFlavor("text/unicode");
+ // If available, use the selection clipboard, otherwise use the global one.
+ if (clipboard.supportsSelectionClipboard())
+ clipboard.getData(trans, clipboard.kSelectionClipboard);
+ else
+ clipboard.getData(trans, clipboard.kGlobalClipboard);
+
+ var data = {};
+ trans.getTransferData("text/unicode", data, {});
+
+ if (data.value) {
+ data = data.value.QueryInterface(Ci.nsISupportsString);
+ url = data.data;
+ }
+ } catch (ex) {
+ }
+
+ return url;
+}
+
+/**
+ * Open the View Source dialog.
+ *
+ * @param aArgsOrDocument
+ * Either an object or a Document. Passing a Document is deprecated,
+ * and is not supported with e10s. This function will throw if
+ * aArgsOrDocument is a CPOW.
+ *
+ * If aArgsOrDocument is an object, that object can take the
+ * following properties:
+ *
+ * URL (required):
+ * A string URL for the page we'd like to view the source of.
+ * browser (optional):
+ * The browser containing the document that we would like to view the
+ * source of. This is required if outerWindowID is passed.
+ * outerWindowID (optional):
+ * The outerWindowID of the content window containing the document that
+ * we want to view the source of. You only need to provide this if you
+ * want to attempt to retrieve the document source from the network
+ * cache.
+ * lineNumber (optional):
+ * The line number to focus on once the source is loaded.
+ */
+function BrowserViewSourceOfDocument(aArgsOrDocument) {
+ if (aArgsOrDocument instanceof Document) {
+ // Deprecated API - callers should pass args object instead.
+ if (Cu.isCrossProcessWrapper(aArgsOrDocument)) {
+ throw new Error("BrowserViewSourceOfDocument cannot accept a CPOW " +
+ "as a document.");
+ }
+
+ let requestor = aArgsOrDocument.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor);
+ let browser = requestor.getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ let outerWindowID = requestor.getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+ let URL = browser.currentURI.spec;
+ aArgsOrDocument = { browser, outerWindowID, URL };
+ }
+
+ gViewSourceUtils.viewSource(aArgsOrDocument);
+}
+
+/**
+ * Opens the View Source dialog for the source loaded in the root
+ * top-level document of the browser.
+ *
+ * @param aBrowser
+ * The browser that we want to load the source of.
+ */
+function BrowserViewSource(aBrowser) {
+ gViewSourceUtils.viewSource({
+ browser: aBrowser,
+ outerWindowID: aBrowser.outerWindowID,
+ URL: aBrowser.currentURI.spec,
+ });
+}
+
+// documentURL - URL of the document to view, or null for this window's document
+// initialTab - id of the initial tab to display, or null for the first tab
+// imageElement - image to load in the Media Tab of the Page Info window;
+// can be null/omitted
+// frameOuterWindowID - the id of the frame that the context menu opened in;
+// can be null/omitted
+// browser - the browser containing the document we're interested in inspecting;
+// can be null/omitted
+function BrowserPageInfo(documentURL, initialTab, imageElement,
+ frameOuterWindowID, browser) {
+ if (documentURL instanceof HTMLDocument) {
+ Deprecated.warning("Please pass the location URL instead of the document " +
+ "to BrowserPageInfo() as the first argument.",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1575830");
+ documentURL = documentURL.location;
+ }
+
+ let args = { initialTab, imageElement, frameOuterWindowID, browser };
+ var windows = Services.wm.getEnumerator("Browser:page-info");
+
+ documentURL = documentURL || window.gBrowser.selectedBrowser.currentURI.spec;
+
+ // Check for windows matching the url.
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ if (win.closed) {
+ continue;
+ }
+ if (win.document.documentElement
+ .getAttribute("relatedUrl") == documentURL) {
+ win.focus();
+ win.resetPageInfo(args);
+ return win;
+ }
+ }
+ // We didn't find a matching window, so open a new one.
+ return window.openDialog("chrome://navigator/content/pageinfo/pageInfo.xul",
+ "_blank",
+ "chrome,dialog=no,resizable",
+ args);
+}
+
+function hiddenWindowStartup()
+{
+ // focus the hidden window
+ window.focus();
+
+ // Disable menus which are not appropriate
+ var disabledItems = ['cmd_close', 'cmd_sendPage', 'Browser:SendLink',
+ 'Browser:EditPage', 'Browser:SavePage', 'cmd_printSetup',
+ 'cmd_print', 'canGoBack', 'canGoForward',
+ 'Browser:AddBookmark', 'Browser:AddBookmarkAs',
+ 'cmd_undo', 'cmd_redo', 'cmd_cut', 'cmd_copy',
+ 'cmd_paste', 'cmd_delete', 'cmd_selectAll',
+ 'cmd_findTypeText', 'cmd_findTypeLinks', 'cmd_find',
+ 'cmd_findNext', 'cmd_findPrev', 'menu_Toolbars',
+ 'menuitem_reload', 'menu_UseStyleSheet', 'charsetMenu',
+ 'View:PageSource', 'View:PageInfo', 'menu_translate',
+ 'cookie_deny', 'cookie_default', 'View:FullScreen',
+ 'cookie_session', 'cookie_allow', 'image_deny',
+ 'image_default', 'image_allow', 'popup_deny',
+ 'popup_default','popup_allow', 'menu_zoom',
+ 'cmd_minimizeWindow', 'cmd_zoomWindow'];
+ var broadcaster;
+
+ for (var id in disabledItems) {
+ broadcaster = document.getElementById(disabledItems[id]);
+ if (broadcaster)
+ broadcaster.setAttribute("disabled", "true");
+ }
+
+ // also hide the window list separator
+ var separator = document.getElementById("sep-window-list");
+ if (separator)
+ separator.setAttribute("hidden", "true");
+
+ // init string bundles
+ gNavigatorBundle = document.getElementById("bundle_navigator");
+ gNavigatorRegionBundle = document.getElementById("bundle_navigator_region");
+ gBrandBundle = document.getElementById("bundle_brand");
+}
+
+function checkForDirectoryListing()
+{
+ if ( "HTTPIndex" in content &&
+ content.HTTPIndex instanceof Ci.nsIHTTPIndex ) {
+ var forced = getBrowser().docShell.forcedCharset;
+ if (forced) {
+ content.defaultCharacterset = forced;
+ }
+ }
+}
+
+function URLBarSetURI(aURI, aValid) {
+ var uri = aURI || getWebNavigation().currentURI;
+ var value;
+
+ // If the url has "wyciwyg://" as the protocol, strip it off.
+ // Nobody wants to see it on the urlbar for dynamically generated pages.
+ try {
+ uri = Services.uriFixup.createExposableURI(uri);
+ } catch (ex) {}
+
+ // Replace "about:blank" and other initial pages with an empty string
+ // only if there's no opener (bug 370555).
+ if (gInitialPages.has(uri.spec))
+ value = (content.opener || getWebNavigation().canGoBack) ? uri.spec : "";
+ else
+ value = losslessDecodeURI(uri);
+
+ gURLBar.value = value;
+ // In some cases, setting the urlBar value causes userTypedValue to
+ // become set because of oninput, so reset it to null.
+ getBrowser().userTypedValue = null;
+
+ SetPageProxyState((value && (!aURI || aValid)) ? "valid" : "invalid", uri);
+}
+
+function losslessDecodeURI(aURI) {
+ var value = aURI.spec;
+ var scheme = aURI.scheme;
+
+ var decodeASCIIOnly = !["https", "http", "file", "ftp"].includes(scheme);
+ // Try to decode as UTF-8 if there's no encoding sequence that we would break.
+ if (!/%25(?:3B|2F|3F|3A|40|26|3D|2B|24|2C|23)/i.test(value)) {
+ if (decodeASCIIOnly) {
+ // This only decodes ascii characters (hex) 20-7e, except 25 (%).
+ // This avoids both cases stipulated below (%-related issues, and \r, \n
+ // and \t, which would be %0d, %0a and %09, respectively) as well as any
+ // non-US-ascii characters.
+ value = value.replace(/%(2[0-4]|2[6-9a-f]|[3-6][0-9a-f]|7[0-9a-e])/g, decodeURI);
+ } else {
+ try {
+ value = decodeURI(value)
+ // 1. decodeURI decodes %25 to %, which creates unintended
+ // encoding sequences. Re-encode it, unless it's part of
+ // a sequence that survived decodeURI, i.e. one for:
+ // ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#'
+ // (RFC 3987 section 3.2)
+ // 2. Ee-encode all adjacent whitespace, to prevent spoofing
+ // attempts where invisible characters would push part of
+ // the URL to overflow the location bar (bug 1395508).
+ .replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|\s(?=\s)|\s$/ig,
+ encodeURIComponent);
+ } catch (e) {}
+ }
+ }
+
+ // Encode invisible characters (soft hyphen, zero-width space, BOM,
+ // line and paragraph separator, word joiner, invisible times,
+ // invisible separator, object replacement character,
+ // C0/C1 controls). (bug 452979, bug 909264)
+ // Encode bidirectional formatting characters.
+ // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
+ // Re-encode whitespace so that it doesn't get eaten away
+ // by the location bar (bug 410726).
+ return value.replace(/[\u0000-\u001f\u007f-\u00a0\u00ad\u034f\u061c\u115f\u1160\u17b4\u17b5\u180b-\u180d\u200b\u200e\u200f\u2028-\u202e\u2060-\u206f\u3164\ufe00-\ufe0f\ufeff\uffa0\ufff0-\ufff8\ufffc]|\ud834[\udd73-\udd7a]|[\udb40-\udb43][\udc00-\udfff]/g, encodeURIComponent);
+}
+
+/**
+ * Use Stylesheet functions.
+ * Written by Tim Hill (bug 6782)
+ * Frameset handling by Neil Rashbrook <neil@parkwaycc.co.uk>
+ **/
+/**
+ * Adds this frame's stylesheet sets to the View > Use Style submenu
+ *
+ * If the frame has no preferred stylesheet set then the "Default style"
+ * menuitem should be shown. Note that it defaults to checked, hidden.
+ *
+ * If this frame has a selected stylesheet set then its menuitem should
+ * be checked (unless the "None" style is currently selected), and the
+ * "Default style" menuitem should to be unchecked.
+ *
+ * The stylesheet sets may match those of other frames. In that case, the
+ * checkmark should be removed from sets that are not selected in this frame.
+ *
+ * @param menuPopup The submenu's popup child
+ * @param frame The frame whose sets are to be added
+ * @param styleDisabled True if the "None" style is currently selected
+ * @param itemPersistentOnly The "Default style" menuitem element
+ */
+function stylesheetFillFrame(menuPopup, frame, styleDisabled, itemPersistentOnly)
+{
+ if (!frame.document.preferredStyleSheetSet)
+ itemPersistentOnly.hidden = false;
+
+ var title = frame.document.selectedStyleSheetSet;
+ if (title)
+ itemPersistentOnly.removeAttribute("checked");
+
+ var styleSheetSets = frame.document.styleSheetSets;
+ for (var i = 0; i < styleSheetSets.length; i++) {
+ var styleSheetSet = styleSheetSets[i];
+ var menuitem = menuPopup.getElementsByAttribute("data", styleSheetSet).item(0);
+ if (menuitem) {
+ if (styleSheetSet != title)
+ menuitem.removeAttribute("checked");
+ } else {
+ var menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("type", "radio");
+ menuItem.setAttribute("label", styleSheetSet);
+ menuItem.setAttribute("data", styleSheetSet);
+ menuItem.setAttribute("checked", styleSheetSet == title && !styleDisabled);
+ menuPopup.appendChild(menuItem);
+ }
+ }
+}
+/**
+ * Adds all available stylesheet sets to the View > Use Style submenu
+ *
+ * If all frames have preferred stylesheet sets then the "Default style"
+ * menuitem should remain hidden, otherwise it should be shown, and
+ * if some frames have a selected stylesheet then the "Default style"
+ * menuitem should be unchecked, otherwise it should remain checked.
+ *
+ * A stylesheet set's menuitem should not be checked if the "None" style
+ * is currently selected. Otherwise a stylesheet set may be available in
+ * more than one frame. In such a case the menuitem should only be checked
+ * if it is selected in all frames in which it is available.
+ *
+ * @param menuPopup The submenu's popup child
+ * @param frameset The frameset whose sets are to be added
+ * @param styleDisabled True if the "None" style is currently selected
+ * @param itemPersistentOnly The "Default style" menuitem element
+ */
+function stylesheetFillAll(menuPopup, frameset, styleDisabled, itemPersistentOnly)
+{
+ stylesheetFillFrame(menuPopup, frameset, styleDisabled, itemPersistentOnly);
+ for (var i = 0; i < frameset.frames.length; i++) {
+ stylesheetFillAll(menuPopup, frameset.frames[i], styleDisabled, itemPersistentOnly);
+ }
+}
+/**
+ * Populates the View > Use Style submenu with all available stylesheet sets
+ * @param menuPopup The submenu's popup child
+ */
+function stylesheetFillPopup(menuPopup)
+{
+ /* Clear menu */
+ var itemPersistentOnly = menuPopup.firstChild.nextSibling;
+ while (itemPersistentOnly.nextSibling)
+ itemPersistentOnly.nextSibling.remove();
+
+ /* Reset permanent items */
+ var styleDisabled = getMarkupDocumentViewer().authorStyleDisabled;
+ menuPopup.firstChild.setAttribute("checked", styleDisabled);
+ itemPersistentOnly.setAttribute("checked", !styleDisabled);
+ itemPersistentOnly.hidden = true;
+
+ stylesheetFillAll(menuPopup, window.content, styleDisabled, itemPersistentOnly);
+}
+/**
+ * Switches all frames in a frameset to the same stylesheet set
+ *
+ * Only frames that support the given title will be switched
+ *
+ * @param frameset The frameset whose frames are to be switched
+ * @param title The name of the stylesheet set to switch to
+ */
+function stylesheetSwitchAll(frameset, title) {
+ if (!title || frameset.document.styleSheetSets.contains(title)) {
+ frameset.document.selectedStyleSheetSet = title;
+ }
+ for (var i = 0; i < frameset.frames.length; i++) {
+ stylesheetSwitchAll(frameset.frames[i], title);
+ }
+}
+
+function setStyleDisabled(disabled) {
+ getMarkupDocumentViewer().authorStyleDisabled = disabled;
+}
+
+function URLBarFocusHandler(aEvent)
+{
+ if (gIgnoreFocus)
+ gIgnoreFocus = false;
+ else if (gClickSelectsAll)
+ gURLBar.select();
+}
+
+function URLBarMouseDownHandler(aEvent)
+{
+ if (gURLBar.hasAttribute("focused")) {
+ gIgnoreClick = true;
+ } else {
+ gIgnoreFocus = true;
+ gIgnoreClick = false;
+ gURLBar.setSelectionRange(0, 0);
+ }
+}
+
+function URLBarClickHandler(aEvent)
+{
+ if (!gIgnoreClick && gClickSelectsAll && gURLBar.selectionStart == gURLBar.selectionEnd)
+ if (gClickAtEndSelects || gURLBar.selectionStart < gURLBar.value.length)
+ gURLBar.select();
+}
+
+function ShowAndSelectContentsOfURLBar()
+{
+ if (!isElementVisible(gURLBar)) {
+ BrowserOpenWindow();
+ return;
+ }
+
+ if (gURLBar.value)
+ gURLBar.select();
+ else
+ gURLBar.focus();
+}
+
+// If "ESC" is pressed in the url bar, we replace the urlbar's value with the url of the page
+// and highlight it, unless it is about:blank, where we reset it to "".
+function handleURLBarRevert()
+{
+ var url = getWebNavigation().currentURI.spec;
+ var throbberElement = document.getElementById("navigator-throbber");
+
+ var isScrolling = gURLBar.userAction == "scrolling";
+
+ // don't revert to last valid url unless page is NOT loading
+ // and user is NOT key-scrolling through autocomplete list
+ if (!throbberElement.hasAttribute("busy") && !isScrolling) {
+ URLBarSetURI();
+
+ // If the value isn't empty, select it.
+ if (gURLBar.value)
+ gURLBar.select();
+ }
+
+ // tell widget to revert to last typed text only if the user
+ // was scrolling when they hit escape
+ return isScrolling;
+}
+
+function UpdatePageProxyState()
+{
+ if (gURLBar.value != gLastValidURLStr)
+ SetPageProxyState("invalid", null);
+}
+
+function SetPageProxyState(aState, aURI)
+{
+ if (!gProxyButton)
+ gProxyButton = document.getElementById("page-proxy-button");
+ if (!gProxyFavIcon)
+ gProxyFavIcon = document.getElementById("page-proxy-favicon");
+ if (!gProxyDeck)
+ gProxyDeck = document.getElementById("page-proxy-deck");
+
+ gProxyButton.setAttribute("pageproxystate", aState);
+
+ if (aState == "valid") {
+ gLastValidURLStr = gURLBar.value;
+ gURLBar.addEventListener("input", UpdatePageProxyState);
+ if (gBrowser.shouldLoadFavIcon(aURI)) {
+ var favStr = gBrowser.buildFavIconString(aURI);
+ if (favStr != gProxyFavIcon.src) {
+ gBrowser.loadFavIcon(aURI, "src", gProxyFavIcon);
+ gProxyDeck.selectedIndex = 0;
+ }
+ else gProxyDeck.selectedIndex = 1;
+ }
+ else {
+ gProxyDeck.selectedIndex = 0;
+ gProxyFavIcon.removeAttribute("src");
+ }
+ } else if (aState == "invalid") {
+ gURLBar.removeEventListener("input", UpdatePageProxyState);
+ gProxyDeck.selectedIndex = 0;
+ gProxyFavIcon.removeAttribute("src");
+ }
+}
+
+function handlePageProxyClick(aEvent)
+{
+ switch (aEvent.button) {
+ case 0:
+ // bug 52784 - select location field contents
+ gURLBar.select();
+ break;
+ case 1:
+ // bug 111337 - load url/keyword from clipboard
+ middleMousePaste(aEvent);
+ break;
+ }
+}
+
+function updateComponentBarBroadcaster()
+{
+ var compBarBroadcaster = document.getElementById('cmd_viewcomponentbar');
+ var taskBarBroadcaster = document.getElementById('cmd_viewtaskbar');
+ var compBar = document.getElementById('component-bar');
+ if (taskBarBroadcaster.getAttribute('checked') == 'true') {
+ compBarBroadcaster.removeAttribute('disabled');
+ if (compBar.getAttribute('hidden') != 'true')
+ compBarBroadcaster.setAttribute('checked', 'true');
+ }
+ else {
+ compBarBroadcaster.setAttribute('disabled', 'true');
+ compBarBroadcaster.removeAttribute('checked');
+ }
+}
+
+function updateToolbarStates(aEvent)
+{
+ onViewToolbarsPopupShowing(aEvent);
+ updateComponentBarBroadcaster();
+
+ const tabbarMenuItem = document.getElementById("menuitem_showhide_tabbar");
+ // Make show/hide menu item reflect current state
+ const visibility = gBrowser.getStripVisibility();
+ tabbarMenuItem.setAttribute("checked", visibility);
+
+ // Don't allow the tab bar to be shown/hidden when more than one tab is open
+ // or when we have 1 tab and the autoHide pref is set
+ const disabled = gBrowser.browsers.length > 1 ||
+ Services.prefs.getBoolPref("browser.tabs.autoHide");
+ tabbarMenuItem.setAttribute("disabled", disabled);
+}
+
+function showHideTabbar()
+{
+ const visibility = gBrowser.getStripVisibility();
+ Services.prefs.setBoolPref("browser.tabs.forceHide", visibility);
+ gBrowser.setStripVisibilityTo(!visibility);
+}
+
+function BrowserFullScreen()
+{
+ window.fullScreen = !window.fullScreen;
+}
+
+function onFullScreen()
+{
+ FullScreen.toggle();
+}
+
+function UpdateStatusBarPopupIcon(aEvent)
+{
+ if (aEvent && aEvent.originalTarget != gBrowser.getNotificationBox())
+ return;
+
+ var showIcon = Services.prefs.getBoolPref("privacy.popups.statusbar_icon_enabled");
+ if (showIcon) {
+ var popupIcon = document.getElementById("popupIcon");
+ popupIcon.hidden = !gBrowser.getNotificationBox().popupCount;
+ }
+}
+function StatusbarViewPopupManager()
+{
+ // Open Data Manager permissions pane site and type prefilled to add.
+ toDataManager(hostUrl() + "|permissions|add|popup");
+}
+
+function popupBlockerMenuShowing(event)
+{
+ var separator = document.getElementById("popupMenuSeparator");
+
+ if (separator)
+ separator.hidden = !createShowPopupsMenu(event.target, gBrowser.selectedBrowser);
+}
+
+function WindowIsClosing()
+{
+ var browser = getBrowser();
+ var cn = browser.tabContainer.childNodes;
+ var numtabs = cn.length;
+
+ if (!gPrivate && AppConstants.platform != "macosx" && isClosingLastBrowser()) {
+ let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(closingCanceled, "browser-lastwindow-close-requested");
+ if (closingCanceled.data)
+ return false;
+
+ Services.obs.notifyObservers(null, "browser-lastwindow-close-granted");
+
+ return true;
+ }
+
+ var reallyClose = gPrivate ||
+ browser.warnAboutClosingTabs(browser.closingTabsEnum.ALL);
+
+ for (var i = 0; reallyClose && i < numtabs; ++i) {
+ var ds = browser.getBrowserForTab(cn[i]).docShell;
+
+ if (ds.contentViewer && !ds.contentViewer.permitUnload())
+ reallyClose = false;
+ }
+
+ return reallyClose;
+}
+
+/**
+ * Checks whether this is the last full *browser* window around.
+ * @returns true if closing last browser window, false if not.
+ */
+function isClosingLastBrowser() {
+ // Popups aren't considered full browser windows.
+ if (!toolbar.visible)
+ return false;
+
+ // Figure out if there's at least one other browser window around.
+ var e = Services.wm.getEnumerator("navigator:browser");
+ while (e.hasMoreElements()) {
+ let win = e.getNext();
+ if (!win.closed && win != window && win.toolbar.visible)
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * file upload support
+ */
+
+/* This function returns the URI of the currently focused content frame
+ * or frameset.
+ */
+function getCurrentURI()
+{
+ var focusedWindow = document.commandDispatcher.focusedWindow;
+ var contentFrame = isContentFrame(focusedWindow) ? focusedWindow : window.content;
+
+ var nav = contentFrame.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation);
+ return nav.currentURI;
+}
+
+var gLastOpenUploadDirectory;
+
+function BrowserUploadFile()
+{
+ if (!gLastOpenUploadDirectory) {
+ gLastOpenUploadDirectory = new RememberLastDir("browser.upload");
+ };
+
+ const nsIFilePicker = Ci.nsIFilePicker;
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ fp.init(window, gNavigatorBundle.getString("uploadFile"), nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText | nsIFilePicker.filterImages |
+ nsIFilePicker.filterXML | nsIFilePicker.filterHTML);
+
+ // use a pref to remember the filterIndex selected by the user.
+ fp.filterIndex = gLastOpenUploadDirectory.filterIndex;
+
+ // Use a pref to remember the displayDirectory selected by the user.
+ fp.displayDirectory = gLastOpenUploadDirectory.path;
+
+ fp.open(rv => {
+ if (rv != nsIFilePicker.returnOK || !fp.fileURL) {
+ return;
+ }
+ gLastOpenUploadDirectory.filterIndex = fp.filterIndex;
+ gLastOpenUploadDirectory.path = fp.file.parent.QueryInterface(Ci.nsIFile);
+
+ try {
+ var targetBaseURI = getCurrentURI();
+ // Generate the target URI. We use fileURL.file.leafName to get the
+ // unicode value of the target filename w/o any URI-escaped chars.
+ // this gives the protocol handler the best chance of generating a
+ // properly formatted URI spec. we pass null for the origin charset
+ // parameter since we want the URI to inherit the origin charset
+ // property from targetBaseURI.
+ var leafName = fp.fileURL.QueryInterface(Ci.nsIFileURL).file.leafName;
+ var targetURI = Services.io.newURI(leafName, null, targetBaseURI);
+
+ // ok, start uploading...
+ openDialog("chrome://communicator/content/downloads/uploadProgress.xul", "",
+ "titlebar,centerscreen,minimizable,dialog=no", fp.fileURL, targetURI);
+ } catch (e) {}
+ });
+}
+
+/* This function is called whenever the file menu is about to be displayed.
+ * Enable the upload menu item if appropriate. */
+function updateFileUploadItem()
+{
+ var canUpload = false;
+ try {
+ canUpload = getCurrentURI().schemeIs('ftp');
+ } catch (e) {}
+
+ var item = document.getElementById('Browser:UploadFile');
+ if (canUpload)
+ item.removeAttribute('disabled');
+ else
+ item.setAttribute('disabled', 'true');
+}
+
+function isBidiEnabled()
+{
+ // first check the pref.
+ if (Services.prefs.getBoolPref("bidi.browser.ui", false)) {
+ return true;
+ }
+
+ // now see if the app locale is an RTL one.
+ const isRTL = Services.locale.isAppLocaleRTL;
+
+ if (isRTL) {
+ Services.prefs.setBoolPref("bidi.browser.ui", true);
+ }
+ return isRTL;
+}
+
+function SwitchDocumentDirection(aWindow)
+{
+ aWindow.document.dir = (aWindow.document.dir == "ltr" ? "rtl" : "ltr");
+
+ for (var run = 0; run < aWindow.frames.length; run++)
+ SwitchDocumentDirection(aWindow.frames[run]);
+}
+
+function updateSavePageItems()
+{
+ var autoDownload = Services.prefs
+ .getBoolPref("browser.download.useDownloadDir");
+ goSetMenuValue("savepage", autoDownload ? "valueSave" : "valueSaveAs");
+}
+
+function convertFromUnicode(charset, str)
+{
+ try {
+ var unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ unicodeConverter.charset = charset;
+ str = unicodeConverter.ConvertFromUnicode(str);
+ return str + unicodeConverter.Finish();
+ } catch(ex) {
+ return null;
+ }
+}
+
+function getNotificationBox(aWindow)
+{
+ return aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler.parentNode.wrappedJSObject;
+}
+
+function BrowserToolboxCustomizeInit()
+{
+ SetPageProxyState("invalid", null);
+ toolboxCustomizeInit("main-menubar");
+ PlacesToolbarHelper.customizeStart();
+
+ var splitter = document.getElementById("urlbar-search-splitter");
+ if (splitter)
+ splitter.remove();
+}
+
+function BrowserToolboxCustomizeDone(aToolboxChanged)
+{
+ toolboxCustomizeDone("main-menubar", getNavToolbox(), aToolboxChanged);
+
+ UpdateNavBar();
+
+ // Update the urlbar
+ var value = gBrowser.userTypedValue;
+ if (value == null)
+ URLBarSetURI();
+ else
+ gURLBar.value = value;
+
+ PlacesToolbarHelper.customizeDone();
+}
+
+function BrowserToolboxCustomizeChange(event)
+{
+ toolboxCustomizeChange(getNavToolbox(), event);
+}
+
+var LightWeightThemeWebInstaller = {
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "InstallBrowserTheme":
+ case "PreviewBrowserTheme":
+ case "ResetBrowserThemePreview":
+ // ignore requests from background tabs
+ if (event.target.ownerDocument.defaultView.top != content)
+ return;
+ }
+ switch (event.type) {
+ case "InstallBrowserTheme":
+ this._installRequest(event);
+ break;
+ case "PreviewBrowserTheme":
+ this._preview(event);
+ break;
+ case "ResetBrowserThemePreview":
+ this._resetPreview(event);
+ break;
+ case "pagehide":
+ case "TabSelect":
+ this._resetPreview();
+ break;
+ }
+ },
+
+ get _manager () {
+ delete this._manager;
+ return this._manager = LightweightThemeManager;
+ },
+
+ _installRequest: function (event) {
+ var node = event.target;
+ var data = this._getThemeFromNode(node);
+ if (!data)
+ return;
+
+ if (this._isAllowed(node)) {
+ this._install(data);
+ return;
+ }
+
+ this._removePreviousNotifications();
+ getBrowser().getNotificationBox().lwthemeInstallRequest(
+ node.ownerDocument.location.host,
+ this._install.bind(this, data));
+ },
+
+ _install: function (newTheme) {
+ this._removePreviousNotifications();
+
+ var previousTheme = this._manager.currentTheme;
+ this._manager.currentTheme = newTheme;
+ if (this._manager.currentTheme &&
+ this._manager.currentTheme.id == newTheme.id)
+ getBrowser().getNotificationBox().lwthemeInstallNotification(function() {
+ LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id);
+ LightWeightThemeWebInstaller._manager.currentTheme = previousTheme;
+ });
+ else
+ getBrowser().getNotificationBox().lwthemeNeedsRestart(newTheme.name);
+
+ // We've already destroyed the permission notification,
+ // so tell the former that it's closed already.
+ return true;
+ },
+
+ _removePreviousNotifications: function () {
+ getBrowser().getNotificationBox().removeNotifications(
+ ["lwtheme-install-request", "lwtheme-install-notification"]);
+ },
+
+ _previewWindow: null,
+ _preview: function (event) {
+ if (!this._isAllowed(event.target))
+ return;
+
+ var data = this._getThemeFromNode(event.target);
+ if (!data)
+ return;
+
+ this._resetPreview();
+
+ this._previewWindow = event.target.ownerDocument.defaultView;
+ this._previewWindow.addEventListener("pagehide", this, true);
+ gBrowser.tabContainer.addEventListener("TabSelect", this);
+
+ this._manager.previewTheme(data);
+ },
+
+ _resetPreview: function (event) {
+ if (!this._previewWindow ||
+ event && !this._isAllowed(event.target))
+ return;
+
+ this._previewWindow.removeEventListener("pagehide", this, true);
+ this._previewWindow = null;
+ gBrowser.tabContainer.removeEventListener("TabSelect", this);
+
+ this._manager.resetPreview();
+ },
+
+ _isAllowed: function (node) {
+ var uri = node.ownerDocument.documentURIObject;
+ return Services.perms.testPermission(uri, "install") == Services.perms.ALLOW_ACTION;
+ },
+
+ _getThemeFromNode: function (node) {
+ return this._manager.parseTheme(node.getAttribute("data-browsertheme"),
+ node.baseURI);
+ }
+}
+
+function AddKeywordForSearchField() {
+ var node = document.popupNode;
+ var doc = node.ownerDocument;
+ var charset = doc.characterSet;
+ var title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill",
+ [doc.title]);
+ var description = PlacesUIUtils.getDescriptionFromDocument(doc);
+ var postData = null;
+ var form = node.form;
+ var spec = form.action || doc.documentURI;
+
+ function encodeNameValuePair(aName, aValue) {
+ return encodeURIComponent(aName) + "=" + encodeURIComponent(aValue);
+ }
+
+ let el = null;
+ let type = null;
+ let formData = [];
+ for (var i = 0; i < form.elements.length; i++) {
+ el = form.elements[i];
+
+ if (!el.type) // happens with fieldsets
+ continue;
+
+ if (el == node) {
+ formData.push(encodeNameValuePair(el.name, "") + "%s");
+ continue;
+ }
+
+ type = el.type;
+
+ if (((el instanceof HTMLInputElement && el.mozIsTextField(true)) ||
+ type == "hidden" || type == "textarea") ||
+ ((type == "checkbox" || type == "radio") && el.checked)) {
+ formData.push(encodeNameValuePair(el.name, el.value));
+ } else if (el instanceof HTMLSelectElement && el.selectedIndex >= 0) {
+ for (var j = 0; j < el.options.length; j++) {
+ if (el.options[j].selected)
+ formData.push(encodeNameValuePair(el.name, el.options[j].value));
+ }
+ }
+ }
+
+ if (form.method == "post" &&
+ form.enctype == "application/x-www-form-urlencoded") {
+ postData = formData.join("&");
+ } else { // get
+ spec += spec.includes("?") ? "&" : "?";
+ spec += formData.join("&");
+ }
+
+ PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(spec), title, description, null,
+ null, null, "", postData, charset);
+}
+
+function getCert()
+{
+ var sslStatus = getBrowser().securityUI
+ .QueryInterface(Ci.nsISSLStatusProvider)
+ .SSLStatus;
+
+ return sslStatus && sslStatus.serverCert;
+}
+
+function viewCertificate()
+{
+ var cert = getCert();
+
+ if (cert)
+ {
+ Cc["@mozilla.org/nsCertificateDialogs;1"]
+ .getService(Ci.nsICertificateDialogs)
+ .viewCert(window, cert);
+ }
+}
+
+function openCertManager()
+{
+ toOpenWindowByType("mozilla:certmanager", "chrome://pippki/content/certManager.xul",
+ "resizable,dialog=no,centerscreen");
+}
+
+function onViewSecurityContextMenu()
+{
+ document.getElementById("viewCertificate").disabled = !getCert();
+}
+
+function updateZoomStatus() {
+ let newLabel = Math.round(ZoomManager.zoom * 100) + " %";
+ let zoomStatusElement = document.getElementById("zoomLevel-display");
+ if (zoomStatusElement.getAttribute('label') != newLabel){
+ zoomStatusElement.setAttribute('label', newLabel);
+ }
+}
+
+function zoomIn() {
+ FullZoom.enlarge();
+ updateZoomStatus();
+}
+
+function zoomOut() {
+ FullZoom.reduce();
+ updateZoomStatus();
+}
+
+/**
+ * Determine whether or not a given focused DOMWindow is in the content area.
+ **/
+function isContentFrame(aFocusedWindow) {
+ if (!aFocusedWindow)
+ return false;
+
+ return (aFocusedWindow.top == window.content);
+}
+
+var browserDragAndDrop = {
+ canDropLink: aEvent => Services.droppedLinkHandler.canDropLink(aEvent, true),
+
+ dragOver(aEvent) {
+ if (this.canDropLink(aEvent)) {
+ aEvent.preventDefault();
+ }
+ },
+
+ getTriggeringPrincipal(aEvent) {
+ return Services.droppedLinkHandler.getTriggeringPrincipal(aEvent);
+ },
+
+ dropLinks(aEvent, aDisallowInherit) {
+ return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit);
+ }
+};
diff --git a/comm/suite/browser/navigator.xul b/comm/suite/browser/navigator.xul
new file mode 100644
index 0000000000..89a0dae076
--- /dev/null
+++ b/comm/suite/browser/navigator.xul
@@ -0,0 +1,571 @@
+<?xml version="1.0"?> <!-- -*- Mode: HTML; indent-tabs-mode: nil -*- -->
+
+<!-- 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/. -->
+
+
+<?xml-stylesheet href="chrome://navigator/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/content/places/places.css" type="text/css"?>
+
+<?xul-overlay href="chrome://navigator/content/navigatorOverlay.xul"?>
+<?xul-overlay href="chrome://navigator/content/linkToolbarOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/contentAreaContextOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/sidebar/sidebarOverlay.xul"?>
+<?xul-overlay href="chrome://navigator/content/safeBrowsingOverlay.xul"?>
+<?xul-overlay href="chrome://navigator/content/webDeveloperOverlay.xul"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % navigatorDTD SYSTEM "chrome://navigator/locale/navigator.dtd" >
+%navigatorDTD;
+]>
+
+<window id="main-window"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="Startup()" onunload="Shutdown()"
+ onclose="return WindowIsClosing();"
+ contenttitlesetting="true"
+ title="&mainWindow.title;"
+ titlemodifier="&mainWindow.titlemodifier;"
+ titlemenuseparator="&mainWindow.titlemodifiermenuseparator;"
+ titleprivate="&mainWindow.titleprivate;"
+ toggletoolbar="true"
+ lightweightthemes="true"
+ lightweightthemesfooter="status-bar"
+ windowtype="navigator:browser"
+ macanimationtype="document"
+ retargetdocumentfocus="urlbar"
+ drawtitle="true"
+ persist="screenX screenY width height sizemode">
+
+ <!-- Generic Utility -->
+ <script src="chrome://global/content/viewSourceUtils.js"/>
+
+ <!-- Content Area -->
+ <script src="chrome://navigator/content/nsBrowserStatusHandler.js"/>
+ <script src="chrome://navigator/content/nsBrowserContentListener.js"/>
+ <script src="chrome://communicator/content/contentAreaClick.js"/>
+ <script src="chrome://communicator/content/findUtils.js"/>
+ <script src="chrome://global/content/printUtils.js"/>
+
+ <!-- Navigator -->
+ <script src="chrome://navigator/content/fullScreen.js"/>
+ <script src="chrome://navigator/content/navigatorDD.js"/>
+ <script src="chrome://navigator/content/sessionHistoryUI.js"/>
+
+ <!-- Places Bookmarks Utilities -->
+ <script src="chrome://navigator/content/browser-places.js"/>
+
+ <!-- hook for stringbundle overlays -->
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="findBundle" src="chrome://global/locale/finddialog.properties"/>
+ </stringbundleset>
+
+ <commandset id="commands">
+ <commandset id="findTypeMenuItems"/>
+ <command id="toggleSidebar"/>
+ </commandset>
+
+ <commandset id="mainCommandSet"/> <!-- Firefox extension compatibility -->
+
+ <!-- broadcasters are appended from the overlay -->
+ <broadcasterset id="navBroadcasters"/>
+ <broadcasterset id="mainBroadcasterSet"/> <!-- Firefox extension compatibility -->
+
+ <!-- keys are appended from the overlay -->
+ <keyset id="navKeys">
+ <key id="showHideSidebar"/>
+ </keyset>
+ <keyset id="mainKeyset"/> <!-- Firefox extension compatibility -->
+
+ <popupset id="mainPopupSet">
+ <menupopup id="backMenu"
+ position="after_start"
+ onpopupshowing="return BrowserBackMenu(event);"
+ oncommand="gotoHistoryIndex(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menupopup id="forwardMenu"
+ position="after_start"
+ onpopupshowing="return BrowserForwardMenu(event);"
+ oncommand="gotoHistoryIndex(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <tooltip id="aHTMLTooltip"
+ onpopupshowing="return FillInHTMLTooltip(document.tooltipNode);"/>
+ <menupopup id="sidebarPopup"/>
+
+ <tooltip id="home-button-tooltip" noautohide="true">
+ <vbox id="home-button-tooltip-inner" flex="1"/>
+ </tooltip>
+
+ <menupopup id="toolbar-context-menu"/>
+
+ <menupopup id="feedsPopup" popupanchor="bottomright" popupalign="topright"
+ onpopupshowing="window.XULBrowserWindow.populateFeeds(this);"
+ oncommand="subscribeToFeed(event.target.statusText, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+
+ <!-- for content formfill and password manager -->
+ <panel id="PopupAutoComplete"
+ type="autocomplete-richlistbox"
+ noautofocus="true"
+ hidden="true"/>
+
+ <!-- for date/time picker. consumeoutsideclicks is set to never, so that
+ clicks on the anchored input box are never consumed. -->
+ <panel id="DateTimePickerPanel"
+ type="arrow"
+ hidden="true"
+ orient="vertical"
+ noautofocus="true"
+ norolluponanchor="true"
+ consumeoutsideclicks="never"
+ level="parent">
+ </panel>
+
+ <!-- for invalid form error message -->
+ <panel id="invalid-form-popup" noautofocus="true" hidden="true" level="parent">
+ <description/>
+ </panel>
+
+ <panel id="editBookmarkPanel"
+ type="arrow"
+ animate="false"
+ orient="vertical"
+ ignorekeys="true"
+ hidden="true"
+ onpopupshown="StarUI.panelShown(event);"
+ aria-labelledby="editBookmarkPanelTitle">
+ <row id="editBookmarkPanelHeader" align="center" hidden="true">
+ <vbox align="center">
+ <image id="editBookmarkPanelStarIcon"/>
+ </vbox>
+ <label id="editBookmarkPanelTitle"/>
+ </row>
+ <vbox id="editBookmarkPanelContent" hidden="true"/>
+ <hbox id="editBookmarkPanelBottomButtons">
+ <button id="editBookmarkPanelRemoveButton"
+ class="editBookmarkPanelHeaderButton"
+ oncommand="StarUI.removeBookmarkButtonCommand();"
+ accesskey="&editBookmark.removeBookmark.accessKey;"/>
+ <spacer flex="1"/>
+ <button id="editBookmarkPanelDeleteButton"
+ class="editBookmarkPanelBottomButton"
+ label="&editBookmark.cancel.label;"
+ oncommand="StarUI.cancelButtonOnCommand();"/>
+ <button id="editBookmarkPanelDoneButton"
+ class="editBookmarkPanelBottomButton"
+ label="&editBookmark.done.label;"
+ default="true"
+ oncommand="StarUI.panel.hidePopup();"/>
+ </hbox>
+ </panel>
+
+ <menupopup id="placesContext"/>
+
+ <!-- Bookmarks and history tooltip -->
+ <tooltip id="bhTooltip"/>
+
+ <panel id="notification-popup"
+ type="arrow"
+ animate="false"
+ position="after_start"
+ hidden="true"
+ role="alert"/>
+
+ <menupopup id="popupBlockerMenu"
+ oncommand="popupBlockerMenuCommand(event.target);"
+ onpopupshowing="return popupBlockerMenuShowing(event);"
+ onpopuphiding="RemovePopupsItems(this);"/>
+ <!-- Items are generated, see popupBlockerMenuShowing() -->
+ <menupopup id="popupNotificationMenu"/>
+ <menupopup id="networkProperties"/>
+
+ <menupopup id="security-context-menu"
+ onpopupshowing="onViewSecurityContextMenu();">
+ <menuitem id="viewSecurityInfo"
+ default="true"
+ label="&viewSecurityInfo.label;"
+ accesskey="&viewSecurityInfo.accesskey;"
+ oncommand="BrowserPageInfo(null, 'securityTab');"/>
+ <menuitem id="viewCertificate"
+ label="&viewCertificate.label;"
+ accesskey="&viewCertificate.accesskey;"
+ oncommand="viewCertificate();"/>
+ <menuseparator/>
+ <menuitem id="viewCertManager"
+ label="&viewCertManager.label;"
+ accesskey="&viewCertManager.accesskey;"
+ oncommand="openCertManager();"/>
+ </menupopup>
+
+ <popupnotification id="password-notification" hidden="true">
+ <popupnotificationcontent orient="vertical">
+ <textbox id="password-notification-username"/>
+ <textbox id="password-notification-password" type="password" show-content=""/>
+ <checkbox id="password-notification-visibilityToggle" hidden="true"/>
+ </popupnotificationcontent>
+ </popupnotification>
+
+ </popupset>
+
+ <!-- context menu -->
+ <popupset id="contentAreaContextSet"/>
+
+ <vbox id="titlebar"/>
+
+ <toolbox id="navigator-toolbox" class="toolbox-top" deferattached="true"
+ mode="full" defaultmode="full">
+ <!-- Menu -->
+ <toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar"
+ persist="collapsed" grippytooltiptext="&menuBar.tooltip;" customizable="true"
+ defaultset="menubar-items"
+ mode="icons" iconsize="small"
+ defaultmode="icons" defaulticonsize="small"
+ context="toolbar-context-menu">
+ <toolbaritem id="menubar-items" class="menubar-items" align="center">
+ <menubar id="main-menubar"/>
+ </toolbaritem>
+ </toolbar>
+
+ <toolbar class="toolbar-primary chromeclass-toolbar" id="nav-bar" persist="collapsed"
+ grippytooltiptext="&navigationToolbar.tooltip;"
+ fullscreentoolbar="true" customizable="true"
+ toolbarname="&navbarCmd.label;" accesskey="&navbarCmd.accesskey;"
+ defaultset="back-button,forward-button,reload-button,stop-button,nav-bar-inner,search-button-container,print-button,throbber-box,window-controls"
+ context="toolbar-context-menu">
+
+ <hbox id="window-controls" hidden="true" fullscreencontrol="true">
+ <toolbarbutton id="minimize-button"
+ tooltiptext="&minimizeButton.tooltip;"
+ oncommand="window.minimize();"/>
+
+ <toolbarbutton id="restore-button"
+ tooltiptext="&restoreButton.tooltip;"
+ oncommand="BrowserFullScreen();"/>
+
+ <toolbarbutton id="close-button"
+ tooltiptext="&closeWindow.label;"
+ oncommand="BrowserTryToCloseWindow();"/>
+ </hbox>
+ </toolbar>
+
+ <toolbarset id="customToolbars" context="toolbar-context-menu"/>
+
+ <toolbar id="PersonalToolbar"
+ accesskey="&bookmarksToolbarCmd.accesskey;"
+ class="chromeclass-directories"
+ persist="collapsed"
+ grippytooltiptext="&bookmarksToolbar.tooltip;"
+ toolbarname="&bookmarksToolbarCmd.label;"
+ nowindowdrag="true"
+ customizable="true"
+ defaultset="home-button,separator,bookmarks-button,personal-bookmarks"
+ mode="full"
+ iconsize="small"
+ labelalign="end"
+ defaultmode="full"
+ defaulticonsize="small"
+ defaultlabelalign="end"
+ context="toolbar-context-menu">
+ </toolbar>
+
+ <toolbarpalette id="BrowserToolbarPalette">
+
+ <!-- Nav bar buttons -->
+ <toolbarbutton id="back-button" type="menu-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&backButton.label;"
+ oncommand="if (event.target==this) BrowserBack(event); else gotoHistoryIndex(event);"
+ onclick="checkForMiddleClick(this, event);"
+ context="backMenu"
+ tooltiptext="&backButton.tooltip;">
+ <observes element="canGoBack" attribute="disabled"/>
+ <menupopup context="" onpopupshowing="BrowserBackMenu(event);"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="forward-button" type="menu-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&forwardButton.label;"
+ oncommand="if (event.target==this) BrowserForward(event); else gotoHistoryIndex(event);"
+ onclick="checkForMiddleClick(this, event);"
+ context="forwardMenu"
+ tooltiptext="&forwardButton.tooltip;">
+ <observes element="canGoForward" attribute="disabled"/>
+ <menupopup context="" onpopupshowing="BrowserForwardMenu(event);"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="reload-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&reloadButton.label;"
+ oncommand="BrowserReload(event);"
+ onclick="checkForMiddleClick(this, event);"
+ tooltiptext="&reloadButton.tooltip;"/>
+
+ <toolbarbutton id="stop-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&stopButton.label;"
+ oncommand="BrowserStop();" observes="canStop"
+ disabled="true"
+ tooltiptext="&stopButton.tooltip;">
+ </toolbarbutton>
+
+ <!-- XXXRatty ? class="toolbarbutton-1 chromeclass-toolbar-additional" ? -->
+ <toolbarbutton id="home-button"
+ class="toolbarbutton-1"
+ label="&homeButton.label;"
+ oncommand="BrowserHome(event);"
+ onclick="if (event.button == 1) BrowserHome(event);"
+ tooltip="home-button-tooltip"
+ ondragstart="homeButtonObserver.onDragStart(event);"
+ ondrop="homeButtonObserver.onDrop(event);"
+ ondragenter="event.stopPropagation();"
+ ondragexit="homeButtonObserver.onDragExit(event);"
+ ondragover="homeButtonObserver.onDragOver(event);"/>
+
+ <toolbaritem id="nav-bar-inner"
+ flex="4"
+ persist="width"
+ class="chromeclass-location nav-bar-class"
+ title="&locationBar.title;">
+ <textbox id="urlbar" class="chromeclass-location uri-element" flex="1"
+ type="autocomplete" autocompletesearch="history file"
+ timeout="50" maxrows="6"
+ enablehistory="true" accesskey="&locationBar.accesskey;"
+ defaultSearchEngine="true" tabscrolling="true"
+ showcommentcolumn="true"
+ placeholder="&locationBar.tooltip;"
+ newlines="stripsurroundingwhitespace"
+ aria-label="&locationBar.title;"
+ oninput="gBrowser.userTypedValue = this.value;"
+ ontextentered="return handleURLBarCommand(eventParam, domEvent);"
+ ontextreverted="return handleURLBarRevert();"
+ onfocus="URLBarFocusHandler(event);"
+ onmousedown="URLBarMouseDownHandler(event);"
+ onclick="URLBarClickHandler(event);">
+ <box id="notification-popup-box" hidden="true" align="center"
+ onmousedown="event.stopPropagation();"
+ onclick="event.stopPropagation();">
+ <image id="default-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="geo-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="addons-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="password-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="plugins-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="web-notifications-notification-icon" class="notification-anchor-icon" role="button"/>
+ </box>
+ <deck id="page-proxy-deck"
+ class="urlbar-icons"
+ ondragstart="proxyIconDNDObserver.onDragStart(event);"
+ onclick="handlePageProxyClick(event);">
+ <image id="page-proxy-button"
+ tooltiptext="&proxyIcon.tooltip;"/>
+ <image id="page-proxy-favicon" validate="never"
+ onload="this.parentNode.selectedIndex = 1;
+ event.stopPropagation();"
+ onerror="gBrowser.addToMissedIconCache(this.src);"
+ tooltiptext="&proxyIcon.tooltip;"/>
+ </deck>
+ <hbox id="urlbar-icons" class="urlbar-icons"
+ onmousedown="event.stopPropagation();"
+ onclick="event.stopPropagation();">
+ <image id="feedsButton" hidden="true" popup="feedsPopup"/>
+ <image id="ev-button" hidden="true"
+ onclick="if (event.button == 0) BrowserPageInfo(null, 'securityTab');"/>
+ <image id="star-button"
+ onclick="BookmarkingUI.onClick(event);"/>
+ </hbox>
+ <menupopup id="ubhist-popup" class="autocomplete-history-popup"
+ popupalign="topleft" popupanchor="bottomleft"
+ onpopupshowing="createUBHistoryMenu(event.target);"
+ oncommand="executeUrlBarHistoryCommand(event.target);"/>
+ </textbox>
+ </toolbaritem>
+
+ <toolbaritem id="go-button-container"
+ class="nav-bar-class"
+ title="&goButton.label;">
+ <button id="go-button"
+ class="button-toolbar chromeclass-location"
+ label="&goButton.label;"
+ tooltiptext="&goButton.tooltip;"
+ default="true"
+ oncommand="handleURLBarCommand('none', event);"
+ onclick="checkForMiddleClick(this, event);"
+ ondragover="goButtonObserver.onDragOver(event);"
+ ondrop="goButtonObserver.onDrop(event);"/>
+ </toolbaritem>
+
+ <toolbaritem id="search-button-container"
+ class="nav-bar-class"
+ title="&searchButton.label;">
+ <button id="search-button"
+ class="button-toolbar chromeclass-location"
+ label="&searchButton.label;"
+ tooltiptext="&searchButton.tooltip;"
+ oncommand="BrowserSearch.loadSearch(QualifySearchTerm());"
+ ondragover="searchButtonObserver.onDragOver(event);"
+ ondrop="searchButtonObserver.onDrop(event);"/>
+ </toolbaritem>
+
+ <toolbaritem id="search-container" title="&searchItem.title;"
+ align="center" class="chromeclass-toolbar-additional nav-bar-class"
+ flex="1" persist="width" removable="true">
+ <searchbar id="searchbar" flex="1"/>
+ </toolbaritem>
+
+ <toolbarbutton id="print-button"
+ label="&printButton.label;"
+ tooltiptext="&printButton.tooltip;"/>
+
+ <toolbaritem id="throbber-box"/>
+
+ <!-- "Bookmarks" button on Bookmarks Toolbar -->
+ <toolbarbutton type="menu" id="bookmarks-button"
+ class="bookmark-item" container="true"
+ label="&bookmarksButton.label;"
+ ondragenter="PlacesMenuDNDHandler.onDragEnter(event);"
+ ondragover="PlacesMenuDNDHandler.onDragOver(event);"
+ ondragexit="PlacesMenuDNDHandler.onDragExit(event);"
+ ondrop="PlacesMenuDNDHandler.onDrop(event);">
+ <menupopup id="BMB_bookmarksPopup"
+ placespopup="true"
+ context="placesContext"
+ openInTabs="children"
+ oncommand="BookmarksEventHandler.onCommand(event);"
+ onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
+ onpopupshowing="BookmarksMenu.onPopupShowing(event, 'BMB_');
+ BookmarksEventHandler.onPopupShowing(event);"
+ tooltip="bhTooltip" popupsinherittooltip="true">
+ <menuitem command="Browser:AddBookmark"/>
+ <menuitem command="Browser:AddBookmarkAs"/>
+ <menuitem command="Browser:BookmarkAllTabs"/>
+ <menuitem command="Browser:ManageBookmark"/>
+ <menuseparator/>
+ <menu id="BMB_feedsMenu" class="menu-iconic feedsMenu" command="feedsMenu"
+ label="&feedsMenu.label;" accesskey="&feedsMenu.accesskey;">
+ <menupopup onpopupshowing="window.XULBrowserWindow.populateFeeds(this);"
+ oncommand="subscribeToFeed(event.target.statusText, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </menu>
+ <menuseparator/>
+ <menu id="BMB_bookmarksToolbarFolderMenu"
+ class="menu-iconic bookmark-item"
+ label="&bookmarksToolbarCmd.label;"
+ container="true">
+ <menupopup id="BMB_bookmarksToolbarFolderPopup"
+ placespopup="true"
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=TOOLBAR');"/>
+ </menu>
+ <menu id="BMB_unsortedBookmarksFolderMenu"
+ class="menu-iconic bookmark-item"
+ container="true">
+ <menupopup id="BMB_unsortedBookmarksFolderPopup"
+ placespopup="true"
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=UNFILED_BOOKMARKS');"/>
+ </menu>
+ <menuseparator/>
+ </menupopup>
+ </toolbarbutton>
+
+ <toolbaritem flex="1" id="personal-bookmarks" title="&bookmarksToolbarItem.label;"
+ removable="true">
+ <hbox flex="1"
+ id="PlacesToolbar"
+ context="placesContext"
+ onclick="BookmarksEventHandler.onClick(event, this._placesView);"
+ oncommand="BookmarksEventHandler.onCommand(event);"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <toolbarbutton class="bookmark-item bookmarks-toolbar-customize"
+ mousethrough="never"
+ label="&bookmarksToolbarItem.label;"/>
+ <hbox flex="1">
+ <hbox align="center">
+ <image id="PlacesToolbarDropIndicator"
+ mousethrough="always"
+ collapsed="true"/>
+ </hbox>
+ <scrollbox orient="horizontal"
+ id="PlacesToolbarItems"
+ flex="1"/>
+ <toolbarbutton type="menu"
+ id="PlacesChevron"
+ class="chevron"
+ mousethrough="never"
+ collapsed="true"
+ tooltiptext="&bookmarksToolbarChevron.tooltip;"
+ onpopupshowing="this.parentNode.parentNode
+ ._placesView._onChevronPopupShowing(event);">
+ <menupopup id="PlacesChevronPopup"
+ placespopup="true"
+ tooltip="bhTooltip" popupsinherittooltip="true"
+ context="placesContext"/>
+ </toolbarbutton>
+ </hbox>
+ </hbox>
+ </toolbaritem>
+
+ <!-- see utilityOverlay.xul
+ <toolbarbutton id="sync-button"/> -->
+ </toolbarpalette>
+ </toolbox>
+
+ <hbox flex="1">
+ <vbox id="sidebar-box" class="chromeclass-extrachrome" domfullscreenhidden="true"/>
+ <splitter id="sidebar-splitter" class="chromeclass-extrachrome" domfullscreenhidden="true"/>
+
+ <vbox id="appcontent" flex="1">
+ <findbar id="FindToolbar" browserid="content" domfullscreenhidden="true"/>
+
+ <!-- this box is temporary, pending XBLified <browser> -->
+ <hbox id="browser" flex="1">
+ <tabbrowser id="content"
+ flex="1" contenttooltip="aHTMLTooltip"
+ contentcontextmenu="contentAreaContextMenu"
+ onnewtab="BrowserOpenTab();"
+ onnewtabclick="checkForMiddleClick(this, event);"
+ autocompletepopup="PopupAutoComplete"
+ datetimepicker="DateTimePickerPanel"
+ onbookmarkgroup="PlacesCommandHook.bookmarkCurrentPages();"
+ oncontentclick="return contentAreaClick(event);"
+ oncommand="BrowserOnCommand(event);"/>
+ <!-- The oncommand listener above lets us fix bugs like 401575 which
+ require error page UI to do privileged things, without letting
+ error pages have any privilege themselves. -->
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <panel id="customizeToolbarSheetPopup"/>
+
+ <statusbar id="status-bar" class="chromeclass-status">
+ <statusbarpanel id="component-bar"/>
+ <statusbarpanel id="statusbar-display" label="&statusText.label;" flex="1"/>
+ <statusbarpanel class="statusbarpanel-progress" id="statusbar-progresspanel" collapsed="true">
+ <progressmeter class="progressmeter-statusbar" id="statusbar-icon" mode="normal" value="0"/>
+ </statusbarpanel>
+ <statusbarpanel class="statusbarpanel-iconic-text zoom-button-align" id="zoomOut-button" label="[-]"
+ oncommand="zoomOut();"
+ tooltiptext="&zoomOut.tooltiptext;"/>
+ <statusbarpanel class="statusbarpanel-iconic-text" id="zoomLevel-display" label="" oncommand="zoomReset();"/>
+ <statusbarpanel class="statusbarpanel-iconic-text zoom-button-align" id="zoomIn-button" label="[+]"
+ oncommand="zoomIn();"
+ tooltiptext="&zoomIn.tooltiptext;"/>
+ <statusbarpanel id="popupIcon" class="statusbarpanel-iconic" hidden="true"
+ oncommand="StatusbarViewPopupManager()"
+ tooltiptext="&popupIcon.tooltiptext;"
+ context="popupBlockerMenu"/>
+ <statusbarpanel class="statusbarpanel-iconic" id="offline-status"/>
+ <statusbarpanel class="statusbarpanel-backgroundbox"
+ id="security-button" dir="reverse"
+ context="security-context-menu"
+ oncommand="BrowserPageInfo(null, 'securityTab')"/>
+ </statusbar>
+</window>
diff --git a/comm/suite/browser/navigatorDD.js b/comm/suite/browser/navigatorDD.js
new file mode 100644
index 0000000000..19c4b8f228
--- /dev/null
+++ b/comm/suite/browser/navigatorDD.js
@@ -0,0 +1,121 @@
+/* -*- 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/. */
+
+function htmlEscape(aString)
+{
+ return aString.replace(/&/g, "&amp;")
+ .replace(/>/g, "&gt;")
+ .replace(/</g, "&lt;")
+ .replace(/"/g, "&quot;")
+ .replace(/'/g, "&apos;");
+}
+
+function BeginDragLink(aEvent, aHref, aTitle)
+{
+ var dt = aEvent.dataTransfer;
+ dt.setData("text/x-moz-url", aHref + "\n" + aTitle);
+ dt.setData("text/uri-list", aHref);
+ dt.setData("text/html", "<a href=\"" + htmlEscape(aHref) +
+ "\">" + htmlEscape(aTitle) + "</a>");
+ dt.setData("text/plain", aHref);
+}
+
+function DragLinkOver(aEvent)
+{
+ if (Services.droppedLinkHandler.canDropLink(aEvent, true))
+ aEvent.preventDefault();
+}
+
+var proxyIconDNDObserver = {
+ onDragStart: function (aEvent)
+ {
+ if (gProxyButton.getAttribute("pageproxystate") != "valid")
+ return;
+
+ BeginDragLink(aEvent, window.content.location.href,
+ window.content.document.title);
+ }
+};
+
+var homeButtonObserver = {
+ onDragStart: function (aEvent)
+ {
+ var homepage = GetLocalizedStringPref("browser.startup.homepage",
+ "about:blank");
+
+ if (homepage)
+ {
+ // XXX find a readable title string for homepage,
+ // perhaps do a history lookup.
+ BeginDragLink(aEvent, homepage, homepage);
+ }
+ },
+
+ onDrop: function (aEvent)
+ {
+ aEvent.stopPropagation();
+ // disallow setting home pages that inherit the principal
+ var url = Services.droppedLinkHandler.dropLink(aEvent, {}, true);
+ setTimeout(openHomeDialog, 0, url);
+ },
+
+ onDragOver: function (aEvent)
+ {
+ if (aEvent.target == aEvent.dataTransfer.mozSourceNode)
+ return;
+
+ DragLinkOver(aEvent);
+ aEvent.dropEffect = "link";
+ var statusTextFld = document.getElementById("statusbar-display");
+ statusTextFld.label = gNavigatorBundle.getString("droponhomebutton");
+ },
+
+ onDragExit: function (aEvent)
+ {
+ aEvent.stopPropagation();
+ document.getElementById("statusbar-display").label = "";
+ }
+};
+
+function openHomeDialog(aURL)
+{
+ var promptTitle = gNavigatorBundle.getString("droponhometitle");
+ var promptMsg = gNavigatorBundle.getString("droponhomemsg");
+ var okButton = gNavigatorBundle.getString("droponhomeokbutton");
+ if (Services.prompt.confirmEx(window, promptTitle, promptMsg,
+ (Services.prompt.BUTTON_TITLE_IS_STRING *
+ Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL *
+ Services.prompt.BUTTON_POS_1),
+ okButton, null, null, null,
+ {value: false}) == 0)
+ SetStringPref("browser.startup.homepage", aURL);
+}
+
+var goButtonObserver = {
+ onDragOver: DragLinkOver,
+
+ onDrop: function (aEvent)
+ {
+ var url = Services.droppedLinkHandler.dropLink(aEvent, {});
+
+ getShortcutOrURIAndPostData(url).then(data => {
+ if (data.url)
+ loadURI(data.url, null, data.postData, false);
+ });
+ }
+};
+
+var searchButtonObserver = {
+ onDragOver: DragLinkOver,
+
+ onDrop: function (aEvent)
+ {
+ var name = {};
+ var url = Services.droppedLinkHandler.dropLink(aEvent, name);
+ if (url)
+ BrowserSearch.loadSearch(name.value || url);
+ }
+};
diff --git a/comm/suite/browser/navigatorOverlay.xul b/comm/suite/browser/navigatorOverlay.xul
new file mode 100644
index 0000000000..45e256b123
--- /dev/null
+++ b/comm/suite/browser/navigatorOverlay.xul
@@ -0,0 +1,716 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/viewZoomOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/viewApplyThemeOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/tasksOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/places/placesOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/charsetOverlay.xul"?>
+<?xul-overlay href="chrome://navigator/content/mailNavigatorOverlay.xul"?>
+
+<!DOCTYPE overlay [
+<!ENTITY % navigatorDTD SYSTEM "chrome://navigator/locale/navigator.dtd" >
+%navigatorDTD;
+<!ENTITY % navigatorOverlayDTD SYSTEM "chrome://navigator/locale/navigatorOverlay.dtd">
+%navigatorOverlayDTD;
+<!ENTITY % contentAreaCommandsDTD SYSTEM "chrome://communicator/locale/contentAreaCommands.dtd" >
+%contentAreaCommandsDTD;
+]>
+
+<overlay id="navigatorOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <!-- Navigator -->
+ <script src="chrome://navigator/content/navigator.js"/>
+
+ <!-- Places Bookmarks Utilities -->
+ <script src="chrome://navigator/content/browser-places.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_navigator"
+ src="chrome://navigator/locale/navigator.properties"/>
+ <stringbundle id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundle_navigator_region"
+ src="chrome://navigator-region/locale/region.properties"/>
+ <stringbundle id="bundle_viewZoom"/>
+ <stringbundle id="bundle_viewApplyTheme"/>
+ </stringbundleset>
+
+ <!-- Keysets -->
+ <keyset id="navKeys">
+ <!-- File Menu -->
+ <key id="key_newNavigatorTab" key="&tabCmd.commandkey;" modifiers="accel" command="cmd_newNavigatorTab"/>
+ <key id="key_newNavigator"/>
+ <key id="key_newPrivateWindow"/>
+ <key id="key_restoreTab" key="&recentTabs.commandkey;" modifiers="accel,shift" oncommand="gBrowser.undoCloseTab(0);"/>
+ <key id="key_restoreWindow" key="&recentWindows.commandkey;" modifiers="accel,shift" oncommand="undoCloseWindow();"/>
+ <key id="key_newBlankPage"/>
+ <key id="focusURLBar" key="&openCmd.commandkey;" oncommand="ShowAndSelectContentsOfURLBar();"
+ modifiers="accel"/>
+ <key id="openLocationKb" key="&openCmd.commandkey;" command="Browser:Open" modifiers="accel,shift"/>
+ <key id="openFileKb" key="&openFileCmd.commandkey;" command="Browser:OpenFile" modifiers="accel"/>
+ <key id="key_savePage" key="&savePageCmd.commandkey;" command="Browser:SavePage" modifiers="accel"/>
+ <key id="key_editPage" key="&editPageCmd.commandkey;" command="Browser:EditPage" modifiers="accel"/>
+ <key id="key_print"/>
+ <key id="key_close"/>
+ <key id="key_closeWindow"/>
+
+ <!-- Edit Menu -->
+ <key id="key_undo"/>
+ <key id="key_redo"/>
+ <key id="key_cut"/>
+ <key id="key_copy"/>
+ <key id="key_paste"/>
+ <key id="key_delete"/>
+ <key id="key_delete2"/>
+ <key id="key_selectAll"/>
+ <key id="key_switchTextDirection"/>
+
+ <!-- View Menu -->
+ <key id="key_reload" key="&reloadCmd.commandkey;" oncommand="BrowserReload();" modifiers="accel"/>
+ <key key="&reloadCmd.commandkey;" oncommand="BrowserReloadSkipCache();" modifiers="accel,shift"/>
+ <key id="key_viewSource" key="&pageSourceCmd.commandkey;" command="View:PageSource" modifiers="accel"/>
+ <key id="key_viewInfo" key="&pageInfoCmd.commandkey;" command="View:PageInfo" modifiers="accel"/>
+ <key id="key_viewNextSidebarPanel" keycode="VK_PAGE_DOWN" oncommand="SidebarGetRelativePanel(1);" modifiers="alt" />
+ <key id="key_viewPrevSidebarPanel" keycode="VK_PAGE_UP" oncommand="SidebarGetRelativePanel(-1);" modifiers="alt" />
+
+ <!-- Search Menu -->
+ <keyset id="findKeys"/>
+
+ <!-- Go Menu -->
+ <key keycode="VK_BACK" command="cmd_handleBackspace"/>
+ <key keycode="VK_BACK" command="cmd_handleShiftBackspace" modifiers="shift"/>
+
+ <!-- Bookmarks Menu -->
+ <key id="addBookmarkKb" key="&addCurPageAsCmd.commandkey;" command="Browser:AddBookmark" modifiers="accel,shift"/>
+ <key id="addBookmarkAsKb" key="&addCurPageAsCmd.commandkey;" command="Browser:AddBookmarkAs" modifiers="accel"/>
+ <key id="manBookmarkKb" key="&manBookmarksCmd.commandkey;" command="Browser:ManageBookmark" modifiers="accel"/>
+
+ <!-- Tools Menu -->
+ <key id="searchInternetKb" key="&searchInternet.commandKey;" modifiers="accel,shift" command="Browser:SearchInternet"/>
+
+ <!-- Misc -->
+ <!-- the amazing fishcam, suppress warning by ',' at the beginning of modifiers, see bug 496322 -->
+ <key key="f" modifiers=",control,alt" oncommand="loadURI('http://www.fishcam.com/');"/>
+ <key id="goUpKb" keycode="VK_UP" command="Browser:Up" modifiers="alt"/>
+ <key id="key_gotoHistory"
+#ifndef XP_MACOSX
+ key="&history.commandKey;"
+ modifiers="accel"
+#else
+ key="&historyCmd.key;"
+ modifiers="accel,shift"
+#endif
+ oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/>
+ <keyset id="viewZoomKeys"/>
+ <keyset id="navigationKeys">
+#ifndef XP_MACOSX
+ <key id="goBackKb" keycode="VK_LEFT"
+ command="Browser:Back" modifiers="alt"/>
+ <key id="goForwardKb" keycode="VK_RIGHT"
+ command="Browser:Forward" modifiers="alt"/>
+#else
+ <key id="goBackKb" keycode="VK_LEFT"
+ command="Browser:Back" modifiers="accel"/>
+ <key id="goForwardKb" keycode="VK_RIGHT"
+ command="Browser:Forward" modifiers="accel"/>
+#endif
+#ifndef XP_WIN
+ <key id="goBackKb2" key="&goBackCmd.commandKey;"
+ command="Browser:Back" modifiers="accel"/>
+ <key id="goForwardKb2" key="&goForwardCmd.commandKey;"
+ command="Browser:Forward" modifiers="accel"/>
+#endif
+ <key id="key_stop" keycode="VK_ESCAPE" oncommand="BrowserStop();"/>
+#ifdef XP_MACOSX
+ <key id="key_stop_mac" key="&stopCmd.macCommandKey;"
+ oncommand="BrowserStop();" modifiers="accel"/>
+#endif
+
+#ifndef XP_MACOSX
+ <key keycode="VK_F5" oncommand="BrowserReload();"/>
+ <key keycode="VK_F5"
+ oncommand="BrowserReloadSkipCache();" modifiers="control"/>
+ <key id="goHome" keycode="VK_HOME"
+ oncommand="BrowserHome();" modifiers="alt"/>
+#else
+ <key id="goHome" keycode="VK_HOME"
+ oncommand="BrowserHome();" modifiers="meta"/>
+#endif
+
+#ifdef XP_MACOSX
+ <key keycode="VK_F11" command="View:FullScreen"/>
+ <key id="key_fullScreen" key="&fullScreenCmd.commandKey;"
+ command="View:FullScreen" modifiers="accel,shift"/>
+#else
+ <key id="key_fullScreen" keycode="VK_F11" command="View:FullScreen"/>
+ <key id="key_newTabWithTargetBg" keycode="VK_INSERT"
+ command="cmd_newTabWithTarget"/>
+#endif
+ <key id="key_newTabWithTargetFg" keycode="VK_INSERT"
+ modifiers="alt" command="cmd_newTabWithTarget"/>
+ </keyset>
+ <keyset id="tasksKeys"/>
+ <key id="key_sanitize" keycode="VK_DELETE"
+ command="Tools:Sanitize" modifiers="accel,shift"/>
+#ifdef XP_MACOSX
+ <key id="key_sanitize_mac" keycode="VK_BACK"
+ command="Tools:Sanitize" modifiers="accel,shift"/>
+#endif
+ </keyset>
+
+ <commandset id="commands">
+ <command id="cmd_newNavigatorTab" oncommand="BrowserOpenTab();"/>
+ <command id="cmd_newNavigator"/>
+ <command id="cmd_newPrivateWindow"/>
+ <command id="cmd_newTabWithTarget" oncommand="contentAreaClick(event);"/>
+ <command id="cmd_handleBackspace" oncommand="BrowserHandleBackspace();" />
+ <command id="cmd_handleShiftBackspace" oncommand="BrowserHandleShiftBackspace();" />
+
+ <command id="cmd_newEditor"/>
+ <!-- NOT IMPLEMENTED
+ <command id="cmd_newEditorTemplate"/>
+ <command id="cmd_newEditorDraft"/> -->
+ <command id="Browser:OpenFile" oncommand="BrowserOpenFileWindow();"/>
+ <command id="Browser:SavePage" oncommand="saveDocument(window.content.document, true);"/>
+ <command id="Browser:EditPage" oncommand="editPageOrFrame();" observes="isImage"/>
+ <command id="Browser:UploadFile" oncommand="BrowserUploadFile();"/>
+ <command id="Browser:Open" oncommand="BrowserOpenWindow();"/>
+ <command id="cmd_printSetup" oncommand="PrintUtils.showPageSetup();"/>
+ <command id="cmd_print" oncommand="PrintUtils.printWindow(window.gBrowser.selectedBrowser.outerWindowID, window.gBrowser.selectedBrowser);"/>
+ <command id="cmd_printpreview" oncommand="BrowserPrintPreview();"/>
+ <command id="cmd_close" oncommand="BrowserCloseTabOrWindow()"/>
+ <command id="cmd_closeOtherTabs" oncommand="BrowserCloseOtherTabs()"/>
+ <command id="cmd_closeTabsToTheEnd"
+ oncommand="BrowserCloseTabsToTheEnd();"/>
+ <command id="cmd_closeWindow" oncommand="BrowserTryToCloseWindow()"/>
+
+ <!-- Edit Menu -->
+ <command id="cmd_undo"/>
+ <command id="cmd_redo"/>
+ <command id="cmd_cut"/>
+ <command id="cmd_copy"/>
+ <command id="cmd_paste"/>
+ <command id="cmd_delete"/>
+ <command id="cmd_selectAll" observes="isImage"/>
+ <command id="cmd_switchTextDirection"/>
+ <commandset id="globalEditMenuItems"/>
+ <commandset id="selectEditMenuItems"/>
+ <commandset id="undoEditMenuItems"/>
+ <commandset id="clipboardEditMenuItems"/>
+
+ <!-- Content area context menu -->
+ <command id="cmd_copyLink"/>
+ <command id="cmd_copyImage"/>
+
+ <!-- View Menu -->
+ <command id="View:PageSource" oncommand="BrowserViewSource(gBrowser.selectedBrowser);" observes="isImage"/>
+ <command id="View:PageInfo" oncommand="BrowserPageInfo();"/>
+ <command id="View:FullScreen" oncommand="BrowserFullScreen();"/>
+ <command id="cmd_SwitchDocumentDirection" oncommand="SwitchDocumentDirection(window.content);" />
+
+ <!-- Search Menu -->
+ <command id="cmd_find"
+ oncommand="BrowserFind();"
+ observes="isImage"/>
+ <command id="cmd_findNext"
+ oncommand="BrowserFindAgain(false);"
+ observes="isImage"/>
+ <command id="cmd_findPrev"
+ oncommand="BrowserFindAgain(true);"
+ observes="isImage"/>
+ <command id="cmd_findTypeText" observes="isImage"/>
+ <command id="cmd_findTypeLinks" observes="isImage"/>
+
+ <!-- Bookmarks Menu -->
+ <command id="Browser:AddBookmark"
+ label="&addCurPageCmd.label;" accesskey="&addCurPageCmd.accesskey;"
+ oncommand="PlacesCommandHook.bookmarkPage(gBrowser.selectedBrowser,
+ false);"/>
+ <command id="Browser:AddBookmarkAs"
+ label="&addCurPageAsCmd.label;" accesskey="&addCurPageAsCmd.accesskey;"
+ oncommand="PlacesCommandHook.bookmarkPage(gBrowser.selectedBrowser,
+ true);"/>
+ <!-- The command is disabled for the hidden window. Otherwise its enabled
+ state is handled by BookmarksEventHandler.onPopupShowing. -->
+ <command id="Browser:BookmarkAllTabs"
+ label="&addCurTabsAsCmd.label;" accesskey="&addCurTabsAsCmd.accesskey;"
+ oncommand="PlacesCommandHook.bookmarkCurrentPages();"
+ disabled="true"/>
+ <command id="Browser:ManageBookmark"
+ label="&manBookmarksCmd.label;" accesskey="&manBookmarksCmd.accesskey;"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('AllBookmarks');"/>
+ <command id="feedsMenu" disabled="true"/>
+ <commandset id="placesCommands"/>
+
+ <!-- Go Menu -->
+ <command id="Browser:Home" oncommand="BrowserHome(event);"/>
+ <command id="Browser:Back" oncommand="BrowserBack();" observes="canGoBack"/>
+ <command id="Browser:Forward" oncommand="BrowserForward();" observes="canGoForward"/>
+ <command id="Browser:Up" oncommand="BrowserUp();" observes="canGoUp"/>
+ <commandset id="viewZoomCommands"/>
+ <commandset id="tasksCommands"/>
+
+ <!-- Tools Menu -->
+ <command id="Browser:SearchInternet" oncommand="BrowserSearch.webSearch();"/>
+ <command id="Tools:Sanitize"
+ oncommand="Cc['@mozilla.org/suite/suiteglue;1'].getService(Ci.nsISuiteGlue).sanitize(window);"/>
+
+ </commandset>
+
+ <broadcasterset id="navBroadcasters">
+ <broadcaster id="canGoBack" disabled="true"/>
+ <broadcaster id="canGoForward" disabled="true"/>
+ <broadcaster id="canGoUp" disabled="true"/>
+ <broadcaster id="Communicator:WorkMode"/>
+ <broadcaster id="cmd_viewtaskbar"
+ checked="true"
+ oncommand="goToggleToolbar('status-bar', 'cmd_viewtaskbar');
+ updateWindowState();"/>
+ <broadcaster id="cmd_viewcomponentbar" oncommand="goToggleToolbar('component-bar', 'cmd_viewcomponentbar');" checked="true"/>
+ <broadcaster id="isImage"/>
+ </broadcasterset>
+
+ <!-- Menu -->
+ <menubar id="main-menubar" class="chromeclass-menubar">
+ <menu id="menu_File">
+ <menupopup id="menu_FilePopup" onpopupshowing="updateCloseItems();getContentAreaFrameCount();updateSavePageItems();updateFileUploadItem();">
+ <menu id="menu_New">
+ <menupopup id="menu_NewPopup">
+ <!-- From utilityOverlay.xul -->
+ <menuitem id="menu_newNavigatorTab" command="cmd_newNavigatorTab" key="key_newNavigatorTab"
+ label="&tabCmd.label;" accesskey="&tabCmd.accesskey;"/>
+ <menuitem id="menu_newNavigator"/>
+ <menuitem id="menu_newPrivateWindow"/>
+ <menuseparator id="navBeginGlobalNewItems"/>
+ <menuitem id="menu_newEditor" command="cmd_newEditor"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_openLocation"
+ label="&openCmd.label;"
+ accesskey="&openCmd.accesskey;"
+ key="openLocationKb"
+ command="Browser:Open"/>
+ <menuitem id="menu_openFile"
+ label="&openFileCmd.label;"
+ accesskey="&openFileCmd.accesskey;"
+ key="openFileKb"
+ command="Browser:OpenFile"/>
+ <menuitem id="menu_close"/>
+ <menuitem id="menu_closeOtherTabs" command="cmd_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"/>
+ <menuitem id="menu_closeTabsToTheEnd"
+ label="&closeTabsToTheEnd.label;"
+ accesskey="&closeTabsToTheEnd.accesskey;"
+ command="cmd_closeTabsToTheEnd"/>
+ <menuitem id="menu_closeWindow" hidden="true" command="cmd_closeWindow" key="key_closeWindow" label="&closeWindow.label;" accesskey="&closeWindow.accesskey;"/>
+ <menuseparator/>
+ <menuitem id="savepage" valueSaveAs="&savePageAsCmd.label;" valueSave="&savePageCmd.label;"
+ accesskey="&savePageCmd.accesskey;" key="key_savePage" command="Browser:SavePage"/>
+ <menuitem id="saveframe" valueSaveAs="&saveFrameAsCmd.label;" valueSave="&saveFrameCmd.label;"
+ accesskey="&saveFrameCmd.accesskey;" oncommand="saveFrameDocument();" hidden="true"/>
+ <menuseparator id="saveMenuBlockEnd"/>
+ <menuitem label="&editPageCmd.label;" accesskey="&editPageCmd.accesskey;" key="key_editPage" command="Browser:EditPage" />
+ <menuseparator/>
+ <menuitem command="Browser:UploadFile" label="&uploadFile.label;" accesskey="&uploadFile.accesskey;"/>
+ <menuseparator/>
+ <menuitem id="menu_printSetup"/>
+ <menuitem id="menu_printPreview"/>
+ <menuitem id="menu_print"/>
+ <menuseparator/>
+ <menuitem id="offlineGoOfflineCmd"/>
+ </menupopup>
+ </menu>
+
+ <menu id="menu_Edit">
+ <menupopup id="menu_EditPopup">
+ <menuitem id="menu_undo"/>
+ <menuitem id="menu_redo"/>
+ <menuseparator/>
+ <menuitem id="menu_cut"/>
+ <menuitem id="menu_copy"/>
+ <menuitem id="menu_paste"/>
+ <menuitem id="menu_delete"/>
+ <menuseparator/>
+ <menuitem id="menu_selectAll"/>
+ <menuseparator/>
+ <menuitem id="menu_find" label="&findOnCmd.label;"/>
+ <menuitem id="menu_findNext"/>
+ <menuitem id="menu_findPrev"/>
+ <menuseparator/>
+ <menuitem id="menu_findTypeLinks"/>
+ <menuitem id="menu_findTypeText"/>
+
+ <menuseparator id="textfieldDirection-separator" hidden="true"/>
+ <menuitem id="textfieldDirection-swap" hidden="true"/>
+
+ <menuseparator id="menu_PrefsSeparator"/>
+ <menuitem id="menu_preferences" oncommand="goPreferences('navigator_pane')"/>
+ </menupopup>
+ </menu>
+
+ <menu id="menu_View">
+ <menupopup id="menu_View_Popup" onpopupshowing="EnableCharsetMenu();">
+ <menu label="&toolbarsCmd.label;" accesskey="&toolbarsCmd.accesskey;" id="menu_Toolbars">
+ <menupopup id="view_toolbars_popup"
+ onpopupshowing="updateToolbarStates(event);"
+ oncommand="onViewToolbarCommand(event);">
+ <menuitem id="menuitem_showhide_tabbar"
+ label="&tabbarCmd.label;"
+ accesskey="&tabbarCmd.accesskey;"
+ class="menuitem-iconic"
+ type="checkbox"
+ oncommand="showHideTabbar();"
+ checked="true"/>
+ <menuitem id="menuitem_taskbarCmd"
+ label="&taskbarCmd.label;"
+ accesskey="&taskbarCmd.accesskey;"
+ class="menuitem-iconic"
+ type="checkbox"
+ observes="cmd_viewtaskbar"/>
+ <menuitem id="menuitem_componentbarCmd"
+ label="&componentbarCmd.label;"
+ accesskey="&componentbarCmd.accesskey;"
+ class="menuitem-iconic"
+ type="checkbox"
+ observes="cmd_viewcomponentbar"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menuitem_fullScreen"
+ label="&fullScreenCmd.label;"
+ accesskey="&fullScreenCmd.accesskey;"
+ key="key_fullScreen"
+ command="View:FullScreen"/>
+ <menuseparator />
+ <menuitem id="menuitem-stop"
+ label="&stopCmd.label;"
+ accesskey="&stopCmd.accesskey;"
+ disabled="true"
+ oncommand="BrowserStop();"
+ key="key_stop"/>
+ <menuitem id="menuitem_reload"
+ label="&reloadCmd.label;"
+ accesskey="&reloadCmd.accesskey;"
+ key="key_reload"
+ oncommand="BrowserReload(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuseparator />
+
+ <!-- overlayed from viewZoomOverlay.xul -->
+ <menu id="menu_zoom"/>
+
+ <menu id="menu_UseStyleSheet"
+ label="&useStyleSheetMenu.label;"
+ accesskey="&useStyleSheetMenu.accesskey;"
+ disabled="false"
+ observes="isImage">
+ <menupopup id="menupopup_stylesheetFill" onpopupshowing="stylesheetFillPopup(this);"
+ oncommand="stylesheetSwitchAll(window.content, event.target.getAttribute('data')); setStyleDisabled(false);" type="radio">
+ <menuitem id="menu_pageStyleNoStyle"
+ label="&useStyleSheetNone.label;"
+ accesskey="&useStyleSheetNone.accesskey;"
+ oncommand="setStyleDisabled(true); event.stopPropagation();"
+ type="radio"/>
+ <menuitem id="menu_pageStylePersistentOnly"
+ label="&useStyleSheetPersistentOnly.label;"
+ accesskey="&useStyleSheetPersistentOnly.accesskey;"
+ type="radio"/>
+ </menupopup>
+ </menu>
+ <menu id="charsetMenu"
+ onpopupshowing="BrowserUpdateCharsetMenu(this);"
+ oncommand="BrowserSetCharacterSet(event);"/>
+ <menuitem hidden="true" id="documentDirection-swap"
+ label="&bidiSwitchPageDirectionItem.label;"
+ accesskey="&bidiSwitchPageDirectionItem.accesskey;"
+ command="cmd_SwitchDocumentDirection"/>
+ <menuseparator />
+ <menuitem id="menuitem_pageSourceCmd"
+ label="&pageSourceCmd.label;"
+ accesskey="&pageSourceCmd.accesskey;"
+ key="key_viewSource"
+ command="View:PageSource"/>
+ <menuitem id="menuitem_pageInfoCmd"
+ label="&pageInfoCmd.label;"
+ accesskey="&pageInfoCmd.accesskey;"
+ key="key_viewInfo"
+ command="View:PageInfo"/>
+ <menuseparator />
+ <!-- overlayed from viewApplyThemeOverlay.xul -->
+ <menu id="menu_viewApplyTheme"/>
+ </menupopup>
+ </menu>
+
+ <menu id="history-menu"
+ label="&goMenu.label;"
+ accesskey="&goMenu.accesskey;"
+ oncommand="gotoHistoryIndex(event);"
+ onclick="checkForMiddleClick(this, event);">
+ <menupopup id="goPopup"
+ onpopupshowing="updateGoMenu(event);">
+ <menuitem id="historyMenuBack"
+ label="&goBackCmd.label;"
+ accesskey="&goBackCmd.accesskey;"
+ key="goBackKb"
+ oncommand="BrowserBack(event);"
+ onclick="checkForMiddleClick(this, event);"
+ observes="canGoBack"/>
+ <menuitem id="historyMenuForward"
+ label="&goForwardCmd.label;"
+ accesskey="&goForwardCmd.accesskey;"
+ key="goForwardKb"
+ oncommand="BrowserForward(event);"
+ onclick="checkForMiddleClick(this, event);"
+ observes="canGoForward"/>
+ <menuitem id="historyMenuUp"
+ label="&goUpCmd.label;"
+ accesskey="&goUpCmd.accesskey;"
+ key="goUpKb"
+ command="Browser:Up"/>
+ <menuitem id="historyMenuHome"
+ label="&goHomeCmd.label;"
+ accesskey="&goHomeCmd.accesskey;"
+ command="Browser:Home"
+ onclick="checkForMiddleClick(this, event);"
+ key="goHome"/>
+ <menuseparator/>
+ <menu id="menu_recentTabs"
+ label="&recentTabs.label;"
+ accesskey="&recentTabs.accesskey;">
+ <menupopup id="menu_recentTabsPopup"
+ onpopupshowing="event.stopPropagation(); updateRecentTabs(this);"
+ oncommand="gBrowser.undoCloseTab(event.target.value);"/>
+ </menu>
+ <menu id="menu_recentWindows"
+ label="&recentWindows.label;"
+ accesskey="&recentWindows.accesskey;">
+ <menupopup id="menu_recentWindowsPopup"
+ onpopupshowing="event.stopPropagation(); updateRecentWindows(this);"
+ oncommand="undoCloseWindow(event.target.value);"/>
+ </menu>
+ <menuitem id="historyRestoreLastSession"
+ label="&historyRestoreLastSession.label;"
+ accesskey="&historyRestoreLastSession.accesskey;"
+ oncommand="restoreLastSession();"
+ disabled="true"/>
+ <menuseparator/>
+ <menuitem id="menu_showAllHistory"
+ label="&historyCmd.label;"
+ accesskey="&historyCmd.accesskey;"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('History');"
+ key="key_gotoHistory"/>
+ <menuseparator id="startHistorySeparator" hidden="true"/>
+ <menuseparator id="endHistorySeparator" hidden="true"/>
+ <!-- Dead for now.
+ <menuitem id="sync-tabs-menuitem"
+ label="&syncTabsMenu.label;"
+ oncommand="BrowserOpenSyncTabs();"
+ disabled="true"/> -->
+ </menupopup>
+ </menu>
+
+ <menu id="bookmarksMenu"
+ label="&bookmarksMenu.label;"
+ accesskey="&bookmarksMenu.accesskey;"
+ ondragenter="PlacesMenuDNDHandler.onDragEnter(event);"
+ ondragover="PlacesMenuDNDHandler.onDragOver(event);"
+ ondrop="PlacesMenuDNDHandler.onDrop(event);">
+ <menupopup id="bookmarksMenuPopup"
+ placespopup="true"
+ context="placesContext"
+ openInTabs="children"
+ oncommand="BookmarksEventHandler.onCommand(event);"
+ onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
+ onpopupshowing="BookmarksMenu.onPopupShowing(event, '');
+ BookmarksEventHandler.onPopupShowing(event);"
+ tooltip="bhTooltip" popupsinherittooltip="true">
+ <menuitem id="menu_bookmarkThisPage"
+ command="Browser:AddBookmark"
+ key="addBookmarkKb"/>
+ <menuitem id="menu_bookmarkThisPageAs"
+ command="Browser:AddBookmarkAs"
+ key="addBookmarkAsKb"/>
+ <menuitem id="menu_bookmarkAllTabs"
+ command="Browser:BookmarkAllTabs"/>
+ <menuitem id="menu_bookmarkManager"
+ command="Browser:ManageBookmark"
+ key="manBookmarkKb"/>
+ <menuseparator id="organizeBookmarksSeparator"/>
+ <menu id="menu_iconic_feedsMenu"
+ label="&feedsMenu.label;"
+ accesskey="&feedsMenu.accesskey;"
+ class="menu-iconic feedsMenu"
+ command="feedsMenu">
+ <menupopup onpopupshowing="window.XULBrowserWindow.populateFeeds(this);"
+ oncommand="subscribeToFeed(event.target.statusText, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </menu>
+ <menuseparator/>
+ <menu id="bookmarksToolbarFolderMenu"
+ class="menu-iconic bookmark-item"
+ label="&bookmarksToolbarCmd.label;"
+ container="true">
+ <menupopup id="bookmarksToolbarFolderPopup"
+ placespopup="true"
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=TOOLBAR');"/>
+ </menu>
+ <menu id="unsortedBookmarksFolderMenu"
+ class="menu-iconic bookmark-item"
+ container="true">
+ <menupopup id="unsortedBookmarksFolderPopup"
+ placespopup="true"
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=UNFILED_BOOKMARKS');"/>
+ </menu>
+ <menuseparator/>
+ </menupopup>
+ </menu>
+
+ <menu id="tasksMenu">
+ <menupopup id="taskPopup">
+ <menuitem id="menu_searchWeb"
+ label="&searchInternetCmd.label;"
+ accesskey="&searchInternetCmd.accesskey;"
+ key="searchInternetKb"
+ command="Browser:SearchInternet"/>
+ <menuitem id="menu_translate"
+ label="&translateMenu.label;"
+ accesskey="&translateMenu.accesskey;"
+ oncommand="Translate();"/>
+ <menu id="menu_cookieManager"
+ label="&cookieCookieManager.label;"
+ accesskey="&cookieCookieManager.accesskey;"
+ oncommand="if (event.target.id.startsWith('cookie_'))
+ CookieImagePopupAction(event.target);">
+ <menupopup id="menupopup_checkPermissions_cookie"
+ onpopupshowing="CheckPermissionsMenu('cookie', this);">
+ <menuitem id="cookie_deny"
+ label="&cookieBlockCookiesCmd.label;"
+ accesskey="&cookieBlockCookiesCmd.accesskey;"
+ title="&cookieMessageTitle.label;"
+ msg="&cookieBlockCookiesMsg.label;"
+ type="radio"
+ name="cookie"/>
+ <menuitem id="cookie_default"
+ label="&cookieCookiesDefaultCmd.label;"
+ accesskey="&cookieCookiesDefaultCmd.accesskey;"
+ title="&cookieMessageTitle.label;"
+ msg="&cookieCookiesDefaultMsg.label;"
+ type="radio"
+ name="cookie"/>
+ <menuitem id="cookie_session"
+ label="&cookieAllowSessionCookiesCmd.label;"
+ accesskey="&cookieAllowSessionCookiesCmd.accesskey;"
+ title="&cookieMessageTitle.label;"
+ msg="&cookieAllowSessionCookiesMsg.label;"
+ type="radio"
+ name="cookie"/>
+ <menuitem id="cookie_allow"
+ label="&cookieAllowCookiesCmd.label;"
+ accesskey="&cookieAllowCookiesCmd.accesskey;"
+ title="&cookieMessageTitle.label;"
+ msg="&cookieAllowCookiesMsg.label;"
+ type="radio"
+ name="cookie"/>
+ <menuseparator/>
+ <menuitem id="menuitem_cookieDisplay"
+ label="&cookieDisplayCookiesCmd.label;"
+ accesskey="&cookieDisplayCookiesCmd.accesskey;"
+ oncommand="toDataManager(hostUrl() + '|cookies');
+ event.stopPropagation();"/>
+ </menupopup>
+ </menu>
+ <menu id="menu_imageManager"
+ label="&cookieImageManager.label;"
+ accesskey="&cookieImageManager.accesskey;"
+ oncommand="if (event.target.id.startsWith('image_'))
+ CookieImagePopupAction(event.target);">
+ <menupopup id="menupopup_checkPermissions_image"
+ onpopupshowing="CheckPermissionsMenu('image', this);">
+ <menuitem id="image_deny"
+ label="&cookieBlockImagesCmd.label;"
+ accesskey="&cookieBlockImagesCmd.accesskey;"
+ title="&cookieImageMessageTitle.label;"
+ msg="&cookieBlockImagesMsg.label;"
+ type="radio"
+ name="image"/>
+ <menuitem id="image_default"
+ label="&cookieImagesDefaultCmd.label;"
+ accesskey="&cookieImagesDefaultCmd.accesskey;"
+ title="&cookieImageMessageTitle.label;"
+ msg="&cookieImagesDefaultMsg.label;"
+ type="radio"
+ name="image"/>
+ <menuitem id="image_allow"
+ label="&cookieAllowImagesCmd.label;"
+ accesskey="&cookieAllowImagesCmd.accesskey;"
+ title="&cookieImageMessageTitle.label;"
+ msg="&cookieAllowImagesMsg.label;"
+ type="radio"
+ name="image"/>
+ <menuseparator/>
+ <menuitem id="menuitem_imageDisplay"
+ label="&cookieDisplayImagesCmd.label;"
+ accesskey="&cookieDisplayImagesCmd.accesskey;"
+ oncommand="toDataManager(hostUrl() + '|permissions|add|image');
+ event.stopPropagation();"/>
+ </menupopup>
+ </menu>
+ <menu id="menu_popupManager"
+ label="&popupsManager.label;"
+ accesskey="&popupsManager.accesskey;"
+ oncommand="if (event.target.id.startsWith('popup_'))
+ CookieImagePopupAction(event.target);">
+ <menupopup id="menupopup_checkForVisibility"
+ onpopupshowing="CheckPermissionsMenu('popup', this);"
+ onpopuphiding="RemovePopupsItems(this);">
+ <menuitem id="popup_deny"
+ label="&popupBlockCmd.label;"
+ accesskey="&popupBlockCmd.accesskey;"
+ title="&popupsMessageChangeTitle.label;"
+ msg="&popupBlockMsg.label;"
+ type="radio"
+ name="popup"/>
+ <menuitem id="popup_default"
+ label="&popupDefaultCmd.label;"
+ accesskey="&popupDefaultCmd.accesskey;"
+ title="&popupsMessageChangeTitle.label;"
+ msg="&popupDefaultMsg.label;"
+ type="radio"
+ name="popup"/>
+ <menuitem id="popup_allow"
+ label="&popupAllowCmd.label;"
+ accesskey="&popupAllowCmd.accesskey;"
+ title="&popupsMessageChangeTitle.label;"
+ msg="&popupAllowMsg.label;"
+ type="radio"
+ name="popup"/>
+ <menuseparator id="popupMenuSeparator"/>
+ <menuitem id="menuitem_PopupsManage"
+ label="&popupsManage.label;"
+ accesskey="&popupsManage.accesskey;"
+ oncommand="toDataManager(hostUrl() + '|permissions|add|popup');
+ event.stopPropagation();"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="navBeginGlobalItems"/>
+ <menuitem id="sanitizeItem"
+ label="&clearPrivateDataCmd.label;"
+ accesskey="&clearPrivateDataCmd.accesskey;"
+ key="key_sanitize" command="Tools:Sanitize"/>
+ </menupopup>
+ </menu>
+
+ <menu id="windowMenu"/>
+
+ <menu id="menu_Help"/>
+ </menubar>
+
+</overlay>
diff --git a/comm/suite/browser/nsBrowserContentHandler.js b/comm/suite/browser/nsBrowserContentHandler.js
new file mode 100644
index 0000000000..5e6c37380f
--- /dev/null
+++ b/comm/suite/browser/nsBrowserContentHandler.js
@@ -0,0 +1,634 @@
+/* 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 {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const nsISupports = Ci.nsISupports;
+const nsIBrowserDOMWindow = Ci.nsIBrowserDOMWindow;
+const nsIBrowserHistory = Ci.nsIBrowserHistory;
+const nsIBrowserSearchService = Ci.nsIBrowserSearchService;
+const nsIChannel = Ci.nsIChannel;
+const nsICommandLine = Ci.nsICommandLine;
+const nsICommandLineHandler = Ci.nsICommandLineHandler;
+const nsICommandLineValidator = Ci.nsICommandLineValidator;
+const nsIComponentRegistrar = Ci.nsIComponentRegistrar;
+const nsIContentHandler = Ci.nsIContentHandler;
+const nsIDOMWindow = Ci.nsIDOMWindow;
+const nsIFactory = Ci.nsIFactory;
+const nsIFileURL = Ci.nsIFileURL;
+const nsIHttpProtocolHandler = Ci.nsIHttpProtocolHandler;
+const nsINetUtil = Ci.nsINetUtil;
+const nsIPrefService = Ci.nsIPrefService;
+const nsIPrefBranch = Ci.nsIPrefBranch;
+const nsIPrefLocalizedString = Ci.nsIPrefLocalizedString;
+const nsISupportsString = Ci.nsISupportsString;
+const nsIWindowMediator = Ci.nsIWindowMediator;
+const nsIWebNavigationInfo = Ci.nsIWebNavigationInfo;
+
+const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
+
+const URI_INHERITS_SECURITY_CONTEXT = nsIHttpProtocolHandler
+ .URI_INHERITS_SECURITY_CONTEXT;
+
+const NS_GENERAL_STARTUP_PREFIX = "@mozilla.org/commandlinehandler/general-startup;1?type=";
+
+function shouldLoadURI(aURI)
+{
+ if (aURI && !aURI.schemeIs("chrome"))
+ return true;
+
+ dump("*** Preventing external load of chrome: URI into browser window\n");
+ dump(" Use -chrome <uri> instead\n");
+ return false;
+}
+
+function resolveURIInternal(aCmdLine, aArgument)
+{
+ try {
+ var file = aCmdLine.resolveFile(aArgument);
+ if (file.exists()) {
+ return Services.io.newFileURI(file);
+ }
+ } catch (e) {
+ }
+
+ // We have interpreted the argument as a relative file URI, but the file
+ // doesn't exist. Try URI fixup heuristics: see bug 290782.
+
+ try {
+ return Services.uriFixup
+ .createFixupURI(aArgument,
+ Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+
+ return null;
+}
+
+function getHomePageGroup()
+{
+ var homePage = Services.prefs.getComplexValue("browser.startup.homepage",
+ nsIPrefLocalizedString).data;
+
+ var count = 0;
+ try {
+ count = Services.prefs.getIntPref("browser.startup.homepage.count");
+ } catch (e) {
+ }
+
+ for (var i = 1; i < count; ++i) {
+ try {
+ homePage += '\n' + Services.prefs.getStringPref("browser.startup.homepage." + i);
+ } catch (e) {
+ }
+ }
+ return homePage;
+}
+
+function needHomePageOverride()
+{
+ var savedmstone = null;
+ try {
+ savedmstone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");
+ if (savedmstone == "ignore")
+ return false;
+ } catch (e) {
+ }
+
+ var mstone = Cc["@mozilla.org/network/protocol;1?name=http"]
+ .getService(nsIHttpProtocolHandler).misc;
+
+ if (mstone == savedmstone)
+ return false;
+
+ Services.prefs.setCharPref("browser.startup.homepage_override.mstone", mstone);
+
+ return true;
+}
+
+function getURLToLoad()
+{
+ if (needHomePageOverride()) {
+ try {
+ return Services.urlFormatter.formatURLPref("startup.homepage_override_url");
+ } catch (e) {
+ }
+ }
+
+ try {
+ var ss = Cc["@mozilla.org/suite/sessionstartup;1"]
+ .getService(Ci.nsISessionStartup);
+ // return about:blank if we are restoring previous session
+ if (ss.doRestore())
+ return "about:blank";
+ } catch (e) {
+ }
+
+ try {
+ var st = Cc["@mozilla.org/suite/sessionstore;1"]
+ .getService(Ci.nsISessionStore);
+ // return about:blank if the last window was closed and should be restored
+ if (st.doRestoreLastWindow())
+ return "about:blank";
+ } catch (e) {
+ }
+
+ try {
+ switch (Services.prefs.getIntPref("browser.startup.page")) {
+ case 1:
+ return getHomePageGroup();
+
+ case 2:
+ return Services.prefs.getStringPref("browser.history.last_page_visited");
+ }
+ } catch (e) {
+ }
+
+ return "about:blank";
+}
+
+function openWindow(parent, url, features, arg)
+{
+ var argstring = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(nsISupportsString);
+ argstring.data = arg;
+ return Services.ww.openWindow(parent, url, "", features, argstring);
+}
+
+function openPreferences()
+{
+ var win = Services.wm.getMostRecentWindow("mozilla:preferences");
+ if (win)
+ win.focus();
+ else
+ openWindow(null, "chrome://communicator/content/pref/preferences.xul",
+ "chrome,titlebar,dialog=no,resizable", "");
+}
+
+function getBrowserURL()
+{
+ try {
+ return Services.prefs.getCharPref("browser.chromeURL");
+ } catch (e) {
+ }
+ return "chrome://navigator/content/navigator.xul";
+}
+
+function handURIToExistingBrowser(aUri, aLocation, aFeatures, aTriggeringPrincipal)
+{
+ if (!shouldLoadURI(aUri))
+ return;
+
+ var navWin = Services.wm.getMostRecentWindow("navigator:browser");
+ if (!navWin) {
+ // if we couldn't load it in an existing window, open a new one
+ openWindow(null, getBrowserURL(), aFeatures, aUri.spec);
+ return;
+ }
+
+ navWin.browserDOMWindow.openURI(aUri, null, aLocation,
+ nsIBrowserDOMWindow.OPEN_EXTERNAL,
+ aTriggeringPrincipal);
+}
+
+function doSearch(aSearchTerm, aFeatures) {
+ var submission = Services.search.defaultEngine.getSubmission(aSearchTerm);
+
+ // fill our nsIMutableArray with uri-as-wstring, null, null, postData
+ var sa = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+
+ var uristring = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(nsISupportsString);
+ uristring.data = submission.uri.spec;
+
+ sa.appendElement(uristring);
+ sa.appendElement(null);
+ sa.appendElement(null);
+ sa.appendElement(submission.postData);
+
+ // XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
+ // preferences, but need nsIBrowserDOMWindow extensions
+ return Services.ww.openWindow(null, getBrowserURL(), "_blank", aFeatures,
+ sa);
+}
+
+var nsBrowserContentHandler = {
+ get wrappedJSObject() {
+ return this;
+ },
+
+ /* nsISupports */
+ QueryInterface: function QueryInterface(iid) {
+ if (iid.equals(nsISupports) ||
+ iid.equals(nsICommandLineHandler) ||
+ iid.equals(nsICommandLine) ||
+ iid.equals(nsICommandLineValidator) ||
+ iid.equals(nsIContentHandler) ||
+ iid.equals(nsIFactory))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ _handledURI: null,
+
+ /* nsICommandLineHandler */
+ handle: function handle(cmdLine) {
+ var features = "chrome,all,dialog=no";
+ try {
+ var width = cmdLine.handleFlagWithParam("width", false);
+ if (width != null)
+ features += ",width=" + width;
+ } catch (e) {
+ }
+ try {
+ var height = cmdLine.handleFlagWithParam("height", false);
+ if (height != null)
+ features += ",height=" + height;
+ } catch (e) {
+ }
+
+ try {
+ var remote = cmdLine.handleFlagWithParam("remote", true);
+ if (/^\s*(\w+)\s*\(\s*([^\s,]+)\s*,?\s*([^\s]*)\s*\)\s*$/.test(remote)) {
+ switch (RegExp.$1.toLowerCase()) {
+ case "openurl":
+ case "openfile":
+ // openURL(<url>)
+ // openURL(<url>,new-window)
+ // openURL(<url>,new-tab)
+
+ var uri = resolveURIInternal(cmdLine, RegExp.$2);
+
+ var location = nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW;
+ if (RegExp.$3 == "new-window")
+ location = nsIBrowserDOMWindow.OPEN_NEWWINDOW;
+ else if (RegExp.$3 == "new-tab")
+ location = nsIBrowserDOMWindow.OPEN_NEWTAB;
+
+ handURIToExistingBrowser(uri, location, features,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ break;
+
+ case "mailto":
+ openWindow(null, "chrome://messenger/content/messengercompose/messengercompose.xul", features, RegExp.$2);
+ break;
+
+ case "xfedocommand":
+ switch (RegExp.$2.toLowerCase()) {
+ case "openbrowser":
+ openWindow(null, getBrowserURL(), features, RegExp.$3 || getURLToLoad());
+ break;
+
+ case "openinbox":
+ openWindow(null, "chrome://messenger/content", features);
+ break;
+
+ case "composemessage":
+ openWindow(null, "chrome://messenger/content/messengercompose/messengercompose.xul", features, RegExp.$3);
+ break;
+
+ default:
+ throw Cr.NS_ERROR_ABORT;
+ }
+ break;
+
+ default:
+ // Somebody sent us a remote command we don't know how to process:
+ // just abort.
+ throw Cr.NS_ERROR_ABORT;
+ }
+
+ cmdLine.preventDefault = true;
+ }
+ } catch (e) {
+ // If we had a -remote flag but failed to process it, throw
+ // NS_ERROR_ABORT so that the xremote code knows to return a failure
+ // back to the handling code.
+ throw Cr.NS_ERROR_ABORT;
+ }
+
+ try {
+ var browserParam = cmdLine.handleFlagWithParam("browser", false);
+ if (browserParam) {
+ openWindow(null, getBrowserURL(), features, browserParam);
+ cmdLine.preventDefault = true;
+ }
+ } catch (e) {
+ if (cmdLine.handleFlag("browser", false)) {
+ openWindow(null, getBrowserURL(), features, getURLToLoad());
+ cmdLine.preventDefault = true;
+ }
+ }
+
+ try {
+ var privateParam = cmdLine.handleFlagWithParam("private", false);
+ if (privateParam) {
+ openWindow(null, getBrowserURL(), "private," + features, privateParam);
+ cmdLine.preventDefault = true;
+ }
+ } catch (e) {
+ if (cmdLine.handleFlag("private", false)) {
+ openWindow(null, getBrowserURL(), "private," + features, "about:privatebrowsing");
+ cmdLine.preventDefault = true;
+ }
+ }
+
+ // If we don't have a profile selected yet (e.g. the Profile Manager is
+ // displayed) we will crash if we open an url and then select a profile. To
+ // prevent this handle all url command line flag and set the command line's
+ // preventDefault to true to prevent the display of the ui. The initial
+ // command line will be retained when nsAppRunner calls LaunchChild though
+ // urls launched after the initial launch will be lost.
+ try {
+ // This will throw when a profile has not been selected.
+ Services.dirsvc.get("ProfD", Ci.nsIFile);
+ } catch (e) {
+ cmdLine.preventDefault = true;
+ throw Cr.NS_ERROR_ABORT;
+ }
+
+ try {
+ var urlParam = cmdLine.handleFlagWithParam("url", false);
+ if (urlParam) {
+ if (this._handledURI == urlParam) {
+ this._handledURI = null;
+ } else {
+ if (cmdLine.handleFlag("requestpending", false) &&
+ cmdLine.state == nsICommandLine.STATE_INITIAL_LAUNCH) {
+ // A DDE request with the URL will follow and the DDE handling code
+ // will send it to the commandline handler via
+ // "mozilla -url http://www.foo.com". Store the URL so we can
+ // ignore this request later
+ this._handledURI = urlParam;
+ }
+
+ urlParam = resolveURIInternal(cmdLine, urlParam);
+ handURIToExistingBrowser(urlParam,
+ nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
+ features,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ }
+ cmdLine.preventDefault = true;
+ }
+ } catch (e) {
+ }
+
+ var param;
+ try {
+ while ((param = cmdLine.handleFlagWithParam("new-window", false)) != null) {
+ var uri = resolveURIInternal(cmdLine, param);
+ handURIToExistingBrowser(uri,
+ nsIBrowserDOMWindow.OPEN_NEWWINDOW,
+ features,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ cmdLine.preventDefault = true;
+ }
+ } catch (e) {
+ }
+
+ try {
+ while ((param = cmdLine.handleFlagWithParam("new-tab", false)) != null) {
+ var uri = resolveURIInternal(cmdLine, param);
+ handURIToExistingBrowser(uri,
+ nsIBrowserDOMWindow.OPEN_NEWTAB,
+ features,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ cmdLine.preventDefault = true;
+ }
+ } catch (e) {
+ }
+
+ try {
+ var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
+ if (chromeParam) {
+ // only load URIs which do not inherit chrome privs
+ var uri = resolveURIInternal(cmdLine, chromeParam);
+ if (!Services.netUtils.URIChainHasFlags(uri, URI_INHERITS_SECURITY_CONTEXT)) {
+ openWindow(null, uri.spec, features);
+ cmdLine.preventDefault = true;
+ }
+ }
+ } catch (e) {
+ }
+
+ try {
+ var fileParam = cmdLine.handleFlagWithParam("file", false);
+ if (fileParam) {
+ fileParam = resolveURIInternal(cmdLine, fileParam);
+ handURIToExistingBrowser(fileParam,
+ nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
+ features,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ cmdLine.preventDefault = true;
+ }
+ } catch (e) {
+ }
+
+ var searchParam = cmdLine.handleFlagWithParam("search", false);
+ if (searchParam) {
+ doSearch(searchParam, features);
+ cmdLine.preventDefault = true;
+ }
+
+ if (cmdLine.handleFlag("preferences", false)) {
+ openPreferences();
+ cmdLine.preventDefault = true;
+ }
+
+ if (cmdLine.handleFlag("silent", false))
+ cmdLine.preventDefault = true;
+
+ if (!cmdLine.preventDefault && cmdLine.length) {
+ var arg = cmdLine.getArgument(0);
+ if (!/^-/.test(arg)) {
+ try {
+ arg = resolveURIInternal(cmdLine, arg);
+ handURIToExistingBrowser(arg,
+ nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
+ features,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ cmdLine.preventDefault = true;
+ } catch (e) {
+ }
+ }
+ }
+
+ if (!cmdLine.preventDefault) {
+ this.realCmdLine = cmdLine;
+
+ var prefBranch = Services.prefs.getBranch("general.startup.");
+
+ var startupArray = prefBranch.getChildList("");
+
+ for (var i = 0; i < startupArray.length; ++i) {
+ this.currentArgument = startupArray[i];
+ var contract = NS_GENERAL_STARTUP_PREFIX + this.currentArgument;
+ if (contract in Cc) {
+ // Ignore any exceptions - we can't do anything about them here.
+ try {
+ if (prefBranch.getBoolPref(this.currentArgument)) {
+ var handler = Cc[contract].getService(nsICommandLineHandler);
+ if (handler.wrappedJSObject)
+ handler.wrappedJSObject.handle(this);
+ else
+ handler.handle(this);
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ }
+
+ this.realCmdLine = null;
+ }
+
+ if (!cmdLine.preventDefault) {
+ var homePage = getURLToLoad();
+ if (!/\n/.test(homePage)) {
+ try {
+ let uri = Services.uriFixup.createFixupURI(homePage, 0);
+ handURIToExistingBrowser(uri, nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW, features);
+ cmdLine.preventDefault = true;
+ } catch (e) {
+ }
+ }
+
+ if (!cmdLine.preventDefault) {
+ openWindow(null, getBrowserURL(), features, homePage);
+ cmdLine.preventDefault = true;
+ }
+ }
+
+ },
+
+ /* nsICommandLineValidator */
+ validate: function validate(cmdLine) {
+ var osintFlagIdx = cmdLine.findFlag("osint", false);
+
+ // If the osint flag is not present and we are not called by DDE then we're safe
+ if (cmdLine.state != nsICommandLine.STATE_REMOTE_EXPLICIT &&
+ cmdLine.findFlag("osint", false) == -1)
+ return;
+
+ // Other handlers may use osint so only handle the osint flag if a
+ // flag is also present and the command line is valid.
+ ["url", "news", "compose"].forEach(function(value) {
+ var flagIdx = cmdLine.findFlag(value, false);
+
+ if (flagIdx > -1) {
+ var testExpr = new RegExp("seamonkey" + value + ":");
+ if (cmdLine.length != flagIdx + 2 ||
+ testExpr.test(cmdLine.getArgument(flagIdx + 1)))
+ throw Cr.NS_ERROR_ABORT;
+ cmdLine.handleFlag("osint", false);
+ }
+ });
+ },
+
+ helpInfo: " -browser <url> Open a browser window.\n" +
+ " -private <url> Open a private window.\n" +
+ " -new-window <url> Open <url> in a new browser window.\n" +
+ " -new-tab <url> Open <url> in a new browser tab.\n" +
+ " -url <url> Open the specified url.\n" +
+ " -chrome <url> Open the specified chrome.\n" +
+ " -search <term> Search <term> with your default search engine.\n" +
+ " -preferences Open Preferences dialog.\n",
+
+ /* nsICommandLine */
+ length: 1,
+
+ getArgument: function getArgument(index) {
+ if (index == 0)
+ return this.currentArgument;
+
+ throw Cr.NS_ERROR_INVALID_ARG;
+ },
+
+ findFlag: function findFlag(flag, caseSensitive) {
+ if (caseSensitive)
+ return flag == this.currentArgument ? 0 : -1;
+ return flag.toLowerCase() == this.currentArgument.toLowerCase() ? 0 : -1;
+ },
+
+ removeArguments: function removeArguments(start, end) {
+ // do nothing
+ },
+
+ handleFlag: function handleFlag(flag, caseSensitive) {
+ if (caseSensitive)
+ return flag == this.currentArgument;
+ return flag.toLowerCase() == this.currentArgument.toLowerCase();
+ },
+
+ handleFlagWithParam : function handleFlagWithParam(flag, caseSensitive) {
+ if (this.handleFlag(flag, caseSensitive))
+ throw Cr.NS_ERROR_INVALID_ARG;
+ },
+
+ get state() {
+ return this.realCmdLine.state;
+ },
+
+ get preventDefault() {
+ return this.realCmdLine.preventDefault;
+ },
+
+ set preventDefault(preventDefault) {
+ return this.realCmdLine.preventDefault = preventDefault;
+ },
+
+ get workingDirectory() {
+ return this.realCmdLine.workingDirectory;
+ },
+
+ get windowContext() {
+ return this.realCmdLine.windowContext;
+ },
+
+ resolveFile: function resolveFile(arg) {
+ return this.realCmdLine.resolveFile(arg);
+ },
+
+ resolveURI: function resolveURI(arg) {
+ return this.realCmdLine.resolveURI(arg);
+ },
+
+ /* nsIContentHandler */
+ handleContent: function handleContent(contentType, context, request) {
+ var webNavInfo = Cc["@mozilla.org/webnavigation-info;1"]
+ .getService(nsIWebNavigationInfo);
+ if (!webNavInfo.isTypeSupported(contentType, null))
+ throw NS_ERROR_WONT_HANDLE_CONTENT;
+
+ request.QueryInterface(nsIChannel);
+ handURIToExistingBrowser(request.URI,
+ nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
+ "chrome,all,dialog=no",
+ request.loadInfo.triggeringPrincipal);
+ request.cancel(Cr.NS_BINDING_ABORTED);
+ },
+
+ /* nsIFactory */
+ createInstance: function createInstance(outer, iid) {
+ if (outer != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+
+ return this.QueryInterface(iid);
+ },
+
+ lockFactory: function lockFactory(lock) {
+ /* no-op */
+ }
+};
+
+const BROWSER_CID = Components.ID("{c2343730-dc2c-11d3-98b3-001083010e9b}");
+
+function NSGetFactory(cid) {
+ if (cid.number == BROWSER_CID)
+ return nsBrowserContentHandler;
+ throw Cr.NS_ERROR_FACTORY_NOT_REGISTERED;
+}
diff --git a/comm/suite/browser/nsBrowserContentListener.js b/comm/suite/browser/nsBrowserContentListener.js
new file mode 100644
index 0000000000..7e618f4f85
--- /dev/null
+++ b/comm/suite/browser/nsBrowserContentListener.js
@@ -0,0 +1,138 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+const nsIWebBrowserChrome = Ci.nsIWebBrowserChrome;
+
+function nsBrowserContentListener(toplevelWindow, contentWindow)
+{
+ // this one is not as easy as you would hope.
+ // need to convert toplevelWindow to an XPConnected object, instead
+ // of a DOM-based object, to be able to QI() it to nsIXULWindow
+
+ this.init(toplevelWindow, contentWindow);
+}
+
+/* implements nsIURIContentListener */
+
+nsBrowserContentListener.prototype =
+{
+ init: function(toplevelWindow, contentWindow)
+ {
+ this.toplevelWindow = toplevelWindow;
+ this.contentWindow = contentWindow;
+
+ // hook up the whole parent chain thing
+ var windowDocShell = this.convertWindowToDocShell(toplevelWindow);
+ if (windowDocShell) {
+ windowDocshell
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIURIContentListener)
+ .parentContentListener = this;
+ }
+
+ var registerWindow = false;
+ try {
+ var treeItem = contentWindow.docShell.QueryInterface(Ci.nsIDocShellTreeItem);
+ var treeOwner = treeItem.treeOwner;
+ var interfaceRequestor = treeOwner.QueryInterface(Ci.nsIInterfaceRequestor);
+ var webBrowserChrome = interfaceRequestor.getInterface(nsIWebBrowserChrome);
+ if (webBrowserChrome)
+ {
+ var chromeFlags = webBrowserChrome.chromeFlags;
+ var res = chromeFlags & nsIWebBrowserChrome.CHROME_ALL;
+ var res2 = chromeFlags & nsIWebBrowserChrome.CHROME_DEFAULT;
+ if ( res == nsIWebBrowserChrome.CHROME_ALL || res2 == nsIWebBrowserChrome.CHROME_DEFAULT)
+ {
+ registerWindow = true;
+ }
+ }
+ } catch (ex) {}
+
+ // register ourselves
+ if (registerWindow)
+ {
+ var uriLoader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);
+ uriLoader.registerContentListener(this);
+ }
+ },
+ close: function()
+ {
+ this.contentWindow = null;
+ var uriLoader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);
+
+ uriLoader.unRegisterContentListener(this);
+ },
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIURIContentListener) ||
+ iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ doContent: function(contentType, isContentPreferred, request, contentHandler)
+ {
+ // forward the doContent to our content area webshell
+ var docShell = this.contentWindow.docShell;
+ if (Services.prefs.getIntPref("browser.link.open_external") == nsIBrowserDOMWindow.OPEN_NEWTAB) {
+ var newTab = gBrowser.loadOneTab("about:blank", {
+ inBackground: Services.prefs.getBoolPref("browser.tabs.loadDivertedInBackground")});
+ docShell = gBrowser.getBrowserForTab(newTab).docShell;
+ }
+
+ var contentListener;
+ try {
+ contentListener =
+ docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIURIContentListener);
+ } catch (ex) {
+ dump(ex);
+ }
+
+ if (!contentListener) return false;
+
+ return contentListener.doContent(contentType, isContentPreferred, request, contentHandler);
+
+ },
+
+ isPreferred: function(contentType, desiredContentType)
+ {
+ if (Services.prefs.getIntPref("browser.link.open_external") == nsIBrowserDOMWindow.OPEN_NEWWINDOW)
+ return false;
+
+ try {
+ var webNavInfo =
+ Cc["@mozilla.org/webnavigation-info;1"]
+ .getService(Ci.nsIWebNavigationInfo);
+ return webNavInfo.isTypeSupported(contentType, null);
+ } catch (e) {
+ // XXX propagate failures other than "NS_ERROR_NOT_AVAILABLE"?
+ // This seems to never get called, so not like it matters....
+ return false;
+ }
+ },
+ canHandleContent: function(contentType, isContentPreferred, desiredContentType)
+ {
+ var docShell = this.contentWindow.docShell;
+ var contentListener;
+ try {
+ contentListener =
+ docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIURIContentListener);
+ } catch (ex) {
+ dump(ex);
+ }
+ if (!contentListener) return false;
+
+ return contentListener.canHandleContent(contentType, isContentPreferred, desiredContentType);
+ },
+ convertWindowToDocShell: function(win) {
+ // don't know how to do this
+ return null;
+ },
+ loadCookie: null,
+ parentContentListener: null
+}
diff --git a/comm/suite/browser/nsBrowserStatusHandler.js b/comm/suite/browser/nsBrowserStatusHandler.js
new file mode 100644
index 0000000000..b4699f8f9c
--- /dev/null
+++ b/comm/suite/browser/nsBrowserStatusHandler.js
@@ -0,0 +1,473 @@
+/* -*- 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/. */
+
+function nsBrowserStatusHandler()
+{
+ this.init();
+}
+
+nsBrowserStatusHandler.prototype =
+{
+ // Stored Status, Link and Loading values
+ status : "",
+ defaultStatus : "",
+ jsStatus : "",
+ jsDefaultStatus : "",
+ overLink : "",
+ feeds : [],
+
+ QueryInterface : function(aIID)
+ {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsIXULBrowserWindow) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ init : function()
+ {
+ this.urlBar = document.getElementById("urlbar");
+ this.throbberElement = document.getElementById("navigator-throbber");
+ this.statusMeter = document.getElementById("statusbar-icon");
+ this.statusPanel = document.getElementById("statusbar-progresspanel");
+ this.stopButton = document.getElementById("stop-button");
+ this.stopMenu = document.getElementById("menuitem-stop");
+ this.stopContext = document.getElementById("context-stop");
+ this.statusTextField = document.getElementById("statusbar-display");
+ this.isImage = document.getElementById("isImage");
+ this.securityButton = document.getElementById("security-button");
+ this.evButton = document.getElementById("ev-button");
+ this.feedsMenu = document.getElementById("feedsMenu");
+ this.feedsButton = document.getElementById("feedsButton");
+
+ // Initialize the security button's state and tooltip text
+ const nsIWebProgressListener = Ci.nsIWebProgressListener;
+ this.onSecurityChange(null, null, nsIWebProgressListener.STATE_IS_INSECURE);
+ },
+
+ destroy : function()
+ {
+ // XXXjag to avoid leaks :-/, see bug 60729
+ this.urlBar = null;
+ this.throbberElement = null;
+ this.statusMeter = null;
+ this.statusPanel = null;
+ this.stopButton = null;
+ this.stopMenu = null;
+ this.stopContext = null;
+ this.statusTextField = null;
+ this.isImage = null;
+ this.securityButton = null;
+ this.evButton = null;
+ this.feedsButton = null;
+ this.feedsMenu = null;
+ },
+
+ // nsIXULBrowserWindow
+ setJSStatus : function(status)
+ {
+ this.jsStatus = status;
+ this.updateStatusField();
+ },
+
+ // nsIXULBrowserWindow
+ setJSDefaultStatus : function(status)
+ {
+ this.jsDefaultStatus = status;
+ this.updateStatusField();
+ },
+
+ setDefaultStatus : function(status)
+ {
+ this.defaultStatus = status;
+ this.updateStatusField();
+ },
+
+ // nsIXULBrowserWindow
+ setOverLink : function(link, context)
+ {
+ this.overLink = link;
+ // clear out 'Done' (or other message) on first hover
+ if (this.defaultStatus)
+ this.defaultStatus = "";
+ this.updateStatusField();
+ if (link)
+ this.statusTextField.setAttribute('crop', 'center');
+ else
+ this.statusTextField.setAttribute('crop', 'end');
+ },
+
+ // nsIXULBrowserWindow
+ // Called before links are navigated to to allow us to retarget them if needed.
+ onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
+ return originalTarget;
+ },
+
+ updateStatusField : function()
+ {
+ var text = this.overLink || this.status || this.jsStatus || this.jsDefaultStatus || this.defaultStatus;
+
+ // check the current value so we don't trigger an attribute change
+ // and cause needless (slow!) UI updates
+ if (this.statusTextField.label != text)
+ this.statusTextField.label = text;
+ },
+
+/**
+ * Returns true if |aMimeType| is text-based, false otherwise.
+ *
+ * @param aMimeType
+ * The MIME type to check.
+ *
+ * If adding types to this function, please also check the similar
+ * function in mozilla/toolkit/content/widgets/findbar.xml.
+ */
+ mimeTypeIsTextBased : function(contentType)
+ {
+ return /^text\/|\+xml$/.test(contentType) ||
+ contentType == "application/x-javascript" ||
+ contentType == "application/javascript" ||
+ contentType == "application/xml" ||
+ contentType == "mozilla.application/cached-xul";
+ },
+
+ populateFeeds : function(popup)
+ {
+ // First clear out any old items
+ while (popup.hasChildNodes())
+ popup.lastChild.remove();
+
+ for (var i = 0; i < this.feeds.length; i++) {
+ var link = this.feeds[i];
+ var menuitem = document.createElement("menuitem");
+ menuitem.className = "menuitem-iconic bookmark-item";
+ menuitem.statusText = link.href;
+ menuitem.setAttribute("label", link.title || link.href);
+ popup.appendChild(menuitem);
+ }
+ },
+
+ onFeedAvailable : function(aLink)
+ {
+ this.feeds.push(aLink);
+ this.feedsMenu.removeAttribute("disabled");
+ this.feedsButton.hidden = false;
+ },
+
+ onLinkIconAvailable : function(aHref)
+ {
+ if (aHref && gProxyFavIcon &&
+ Services.prefs.getBoolPref("browser.chrome.site_icons")) {
+ var browser = getBrowser();
+ if (browser.userTypedValue === null)
+ gProxyFavIcon.setAttribute("src", aHref);
+ }
+ },
+
+ onProgressChange : function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress)
+ {
+ if (aMaxTotalProgress > 0) {
+ // This is highly optimized. Don't touch this code unless
+ // you are intimately familiar with the cost of setting
+ // attrs on XUL elements. -- hyatt
+ var percentage = (aCurTotalProgress * 100) / aMaxTotalProgress;
+ this.statusMeter.value = percentage;
+ }
+ },
+
+ onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ const nsIWebProgressListener = Ci.nsIWebProgressListener;
+ const nsIChannel = Ci.nsIChannel;
+ var ctype;
+ if (aStateFlags & nsIWebProgressListener.STATE_START) {
+ // This (thanks to the filter) is a network start or the first
+ // stray request (the first request outside of the document load),
+ // initialize the throbber and his friends.
+
+ // Call start document load listeners (only if this is a network load)
+ if (aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK &&
+ aRequest && aWebProgress.isTopLevel)
+ this.startDocumentLoad(aRequest);
+
+ if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
+ // Show the progress meter
+ this.statusPanel.collapsed = false;
+ // Turn the throbber on.
+ this.throbberElement.setAttribute("busy", "true");
+ }
+
+ // XXX: These need to be based on window activity...
+ this.stopButton.disabled = false;
+ this.stopMenu.removeAttribute('disabled');
+ this.stopContext.removeAttribute('disabled');
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ if (aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+ if (aRequest) {
+ if (aWebProgress.isTopLevel)
+ this.endDocumentLoad(aRequest, aStatus);
+ }
+ }
+
+ // This (thanks to the filter) is a network stop or the last
+ // request stop outside of loading the document, stop throbbers
+ // and progress bars and such
+ if (aRequest) {
+ var msg = "";
+ // Get the channel if the request is a channel
+ if (aRequest instanceof nsIChannel) {
+ var location = aRequest.URI.spec;
+ if (location != "about:blank") {
+ switch (aStatus) {
+ case Cr.NS_BINDING_ABORTED:
+ msg = gNavigatorBundle.getString("nv_stopped");
+ break;
+ case Cr.NS_ERROR_NET_TIMEOUT:
+ msg = gNavigatorBundle.getString("nv_timeout");
+ break;
+ }
+ }
+ }
+ // If msg is false then we did not have an error (channel may have
+ // been null, in the case of a stray image load).
+ if (!msg) {
+ msg = gNavigatorBundle.getString("nv_done");
+ }
+ this.status = "";
+ this.setDefaultStatus(msg);
+
+ // Disable menu entries for images, enable otherwise
+ if (content.document && this.mimeTypeIsTextBased(content.document.contentType))
+ this.isImage.removeAttribute('disabled');
+ else
+ this.isImage.setAttribute('disabled', 'true');
+ }
+
+ // Turn the progress meter and throbber off.
+ this.statusPanel.collapsed = true;
+ this.statusMeter.value = 0; // be sure to clear the progress bar
+ this.throbberElement.removeAttribute("busy");
+
+ // XXX: These need to be based on window activity...
+ // XXXjag: <command id="cmd_stop"/> ?
+ this.stopButton.disabled = true;
+ this.stopMenu.setAttribute('disabled', 'true');
+ this.stopContext.setAttribute('disabled', 'true');
+
+ ZoomListeners.onLocationChange(getBrowser().currentURI);
+ }
+ },
+
+ onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ const nsIWebProgressListener = Ci.nsIWebProgressListener;
+ if (gContextMenu) {
+ // Optimise for the common case
+ if (aWebProgress.isTopLevel)
+ document.getElementById("contentAreaContextMenu").hidePopup();
+ else {
+ for (var contextWindow = gContextMenu.target.ownerDocument.defaultView;
+ contextWindow != contextWindow.parent;
+ contextWindow = contextWindow.parent) {
+ if (contextWindow == aWebProgress.DOMWindow) {
+ document.getElementById("contentAreaContextMenu").hidePopup();
+ break;
+ }
+ }
+ }
+ }
+
+ if (document.tooltipNode) {
+ // Optimise for the common case
+ if (aWebProgress.isTopLevel) {
+ document.getElementById("aHTMLTooltip").hidePopup();
+ document.tooltipNode = null;
+ } else {
+ for (var tooltipWindow = document.tooltipNode.ownerDocument.defaultView;
+ tooltipWindow != tooltipWindow.parent;
+ tooltipWindow = tooltipWindow.parent) {
+ if (tooltipWindow == aWebProgress.DOMWindow) {
+ document.getElementById("aHTMLTooltip").hidePopup();
+ document.tooltipNode = null;
+ break;
+ }
+ }
+ }
+ }
+
+ // Hide the form invalid popup.
+ if (gFormSubmitObserver.panel) {
+ gFormSubmitObserver.panel.hidePopup();
+ }
+
+ // XXX temporary hack for bug 104532.
+ // Depends heavily on setOverLink implementation
+ if (!aRequest)
+ this.status = this.jsStatus = this.jsDefaultStatus = "";
+
+ this.setOverLink("");
+
+ // Disable menu entries for images, enable otherwise
+ if (content.document && this.mimeTypeIsTextBased(content.document.contentType))
+ this.isImage.removeAttribute('disabled');
+ else
+ this.isImage.setAttribute('disabled', 'true');
+
+ // We should probably not do this if the value has changed since the user
+ // searched
+ // Update urlbar only if a new page was loaded on the primary content area
+ // Do not update urlbar if there was a subframe navigation
+
+ var browser = getBrowser().selectedBrowser;
+ if (aWebProgress.isTopLevel) {
+ var userTypedValue = browser.userTypedValue;
+ if (userTypedValue === null) {
+ URLBarSetURI(aLocation, true);
+ } else {
+ this.urlBar.value = userTypedValue;
+ SetPageProxyState("invalid", null);
+ }
+
+ BookmarkingUI.updateStarState();
+
+ this.feedsMenu.setAttribute("disabled", "true");
+ this.feedsButton.hidden = true;
+ this.feeds = [];
+
+ // When background tab comes into foreground or loading a new page
+ // (aRequest set), might want to update zoom.
+ if (FullZoom.updateBackgroundTabs || aRequest){
+ FullZoom.onLocationChange(getBrowser().currentURI, !aRequest, browser);
+ ZoomListeners.onLocationChange(getBrowser().currentURI);
+ }
+ }
+ UpdateBackForwardButtons();
+
+ UpdateStatusBarPopupIcon();
+
+ BrowserSearch.updateSearchButton();
+ },
+
+ onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ this.status = aMessage;
+ this.updateStatusField();
+ },
+
+ onSecurityChange : function(aWebProgress, aRequest, aState)
+ {
+ const wpl = Ci.nsIWebProgressListener;
+ const wpl_security_bits = wpl.STATE_IS_SECURE |
+ wpl.STATE_IS_BROKEN |
+ wpl.STATE_IS_INSECURE;
+
+ var highlightSecure =
+ Services.prefs.getBoolPref("browser.urlbar.highlight.secure");
+
+ /* aState is defined as a bitmask that may be extended in the future.
+ * We filter out any unknown bits before testing for known values.
+ */
+ switch (aState & wpl_security_bits) {
+ case wpl.STATE_IS_SECURE:
+ const nsISSLStatusProvider = Ci.nsISSLStatusProvider;
+ var cert = getBrowser().securityUI.QueryInterface(nsISSLStatusProvider)
+ .SSLStatus.serverCert;
+ var issuerName = cert.issuerOrganization ||
+ cert.issuerCommonName || cert.issuerName;
+ this.securityButton.setAttribute("tooltiptext",
+ gNavigatorBundle.getFormattedString("securityButtonTooltipSecure",
+ [issuerName]));
+ this.securityButton.setAttribute("level", "high");
+ if (highlightSecure)
+ this.urlBar.setAttribute("level", "high");
+ else
+ this.urlBar.removeAttribute("level");
+ break;
+ case wpl.STATE_IS_BROKEN:
+ this.securityButton.setAttribute("tooltiptext",
+ gNavigatorBundle.getString("securityButtonTooltipMixedContent"));
+ this.securityButton.setAttribute("level", "broken");
+ if (highlightSecure)
+ this.urlBar.setAttribute("level", "broken");
+ else
+ this.urlBar.removeAttribute("level");
+ break;
+ case wpl.STATE_IS_INSECURE:
+ default:
+ this.securityButton.setAttribute("tooltiptext",
+ gNavigatorBundle.getString("securityButtonTooltipInsecure"));
+ this.securityButton.removeAttribute("level");
+ this.urlBar.removeAttribute("level");
+ break;
+ }
+
+ if (aState & wpl.STATE_IDENTITY_EV_TOPLEVEL) {
+ var organization =
+ getBrowser().securityUI
+ .QueryInterface(Ci.nsISSLStatusProvider)
+ .SSLStatus
+ .QueryInterface(Ci.nsISSLStatus)
+ .serverCert.organization;
+ this.securityButton.setAttribute("label", organization);
+ this.evButton.setAttribute("tooltiptext", organization);
+ this.evButton.hidden = false;
+ } else {
+ this.securityButton.removeAttribute("label");
+ this.evButton.hidden = true;
+ }
+ },
+
+ startDocumentLoad : function(aRequest)
+ {
+ var uri = aRequest.QueryInterface(Ci.nsIChannel).originalURI;
+
+ // clear out search-engine data
+ getBrowser().selectedBrowser.engines = null;
+
+ // Set the URI now if it isn't already set, so that the user can tell which
+ // site is loading. Only do this if user requested the load via chrome UI,
+ // to minimise spoofing risk.
+ if (!content.opener &&
+ !gURLBar.value &&
+ getWebNavigation().currentURI.spec == "about:blank")
+ URLBarSetURI(uri);
+
+ try {
+ Services.obs.notifyObservers(content, "StartDocumentLoad", uri.spec);
+ } catch (e) {
+ }
+ },
+
+ endDocumentLoad : function(aRequest, aStatus)
+ {
+ const nsIChannel = Ci.nsIChannel;
+ var urlStr = aRequest.QueryInterface(nsIChannel).originalURI.spec;
+
+ if (Components.isSuccessCode(aStatus))
+ dump("Document "+urlStr+" loaded successfully\n"); // per QA request
+ else {
+ // per QA request
+ var e = new Components.Exception("", aStatus);
+ var name = e.name;
+ dump("Error loading URL "+urlStr+" : "+
+ Number(aStatus).toString(16));
+ if (name)
+ dump(" ("+name+")");
+ dump('\n');
+ }
+
+ var notification = Components.isSuccessCode(aStatus) ? "EndDocumentLoad" : "FailDocumentLoad";
+ try {
+ Services.obs.notifyObservers(content, notification, urlStr);
+ } catch (e) {
+ }
+ }
+}
+
diff --git a/comm/suite/browser/nsTypeAheadFind.js b/comm/suite/browser/nsTypeAheadFind.js
new file mode 100644
index 0000000000..aa5e0b9fba
--- /dev/null
+++ b/comm/suite/browser/nsTypeAheadFind.js
@@ -0,0 +1,416 @@
+/* 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/. */
+
+const kSpace = " ".charCodeAt(0);
+const kSlash = "/".charCodeAt(0);
+const kApostrophe = "'".charCodeAt(0);
+
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function findTypeController(aTypeAheadFind, aWindow)
+{
+ this.mTypeAheadFind = aTypeAheadFind;
+ this.mWindow = aWindow;
+}
+
+findTypeController.prototype = {
+ /* nsIController */
+ supportsCommand: function(aCommand) {
+ return aCommand == "cmd_findTypeText" || aCommand == "cmd_findTypeLinks";
+ },
+
+ isCommandEnabled: function(aCommand) {
+ // We can always find if there's a primary content window in which to find.
+ if (this.mWindow.content)
+ return true;
+
+ // We can also find if the focused window is a content window.
+ // Note: this gets called during a focus change
+ // so the new window might not have focus yet.
+ var commandDispatcher = this.mWindow.document.commandDispatcher;
+ var e = commandDispatcher.focusedElement;
+ var w = e ? e.ownerDocument.defaultView : commandDispatcher.focusedWindow;
+ return w.top != this.mWindow;
+ },
+
+ doCommand: function(aCommand) {
+ this.mTypeAheadFind.startFind(this.mWindow, aCommand != "cmd_findTypeText");
+ },
+
+ onEvent: function(aEvent) {
+ }
+}
+
+function typeAheadFind()
+{
+}
+
+typeAheadFind.prototype = {
+ /* properties required for XPCOMUtils */
+ classID: Components.ID("{45c8f75b-a299-4178-a461-f63690389055}"),
+
+ /* members */
+ mBadKeysSinceMatch: 0,
+ mBundle: null,
+ mCurrentWindow: null,
+ mEventTarget: null,
+ mFind: null,
+ mFindService: null,
+ mFound: null,
+ mLinks: false,
+ mSearchString: "",
+ mSelection: null,
+ mTimer: null,
+ mXULBrowserWindow: null,
+
+ /* nsISupports */
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISupportsWeakReference,
+ Ci.nsIObserver,
+ Ci.nsITimerCallback,
+ Ci.nsIDOMEventListener,
+ Ci.nsISelectionListener]),
+
+ /* nsIObserver */
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "app-startup") {
+ // It's now safe to get our pref branch.
+ this.mPrefs = Services.prefs.getBranch("accessibility.typeaheadfind.");
+ // We need to add our event listeners to all windows.
+ Services.ww.registerNotification(this);
+ // We also need to listen for find again commands
+ Services.obs.addObserver(this, "nsWebBrowserFind_FindAgain", true);
+ }
+ if (aTopic == "domwindowopened") {
+ // Add our listeners. They get automatically removed on window teardown.
+ aSubject.controllers.appendController(new findTypeController(this, aSubject));
+ Services.els.addSystemEventListener(aSubject, "keypress", this, false);
+ }
+ if (aTopic == "nsWebBrowserFind_FindAgain" &&
+ aSubject instanceof Ci.nsISupportsInterfacePointer &&
+ aSubject.data instanceof Ci.nsIDOMWindow &&
+ aSubject.data.top == this.mCurrentWindow &&
+ this.mSearchString) {
+ // It's a find again. Was it one that we just searched for?
+ var w = aSubject.data;
+ var find = w.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebBrowserFind);
+ if (find.searchString.toLowerCase() == this.mSearchString) {
+ var reverse = aData == "up";
+ this.stopFind(false);
+ var result = Ci.nsITypeAheadFind.FIND_NOTFOUND;
+ if (!this.mBadKeysSinceMatch)
+ result = this.mFind.findAgain(reverse, this.mLinks);
+ this.showStatusMatch(result, reverse ? "prevmatch" : "nextmatch");
+ // Don't let anyone else try to find again.
+ aSubject.data = null;
+ }
+ }
+ },
+
+ /* nsITimerCallback */
+ notify: function(aTimer) {
+ this.stopFind(false);
+ },
+
+ /* nsIDOMEventListener */
+ handleEvent: function(aEvent) {
+ if (!aEvent.type.startsWith("key")) {
+ this.stopFind(false);
+ return true;
+ }
+
+ // We don't care about these keys.
+ if (aEvent.altKey || aEvent.ctrlKey || aEvent.metaKey)
+ return true;
+
+ if (aEvent.type != "keypress") {
+ aEvent.stopPropagation();
+ return true;
+ }
+
+ // Are we already in a find?
+ if (aEvent.eventPhase == Ci.nsIDOMEvent.CAPTURING_PHASE)
+ return this.processKey(aEvent);
+
+ // Check whether we want to start a new find.
+ if (aEvent.defaultPrevented)
+ return true;
+
+ // We don't want to start a find on a control character.
+ // We also don't want to start on a space, since that scrolls the page.
+ if (aEvent.keyCode || aEvent.charCode <= kSpace)
+ return true;
+
+ // Don't start a find if the focus is an editable element.
+ var window = aEvent.currentTarget;
+ var element = window.document.commandDispatcher.focusedElement;
+ if (element.nodeType == element.ELEMENT_NODE &&
+ element.namespaceURI == "http://www.w3.org/1999/xhtml" &&
+ element.isContentEditable)
+ return true;
+
+ // Don't start a find if the focus is on a form element.
+ if ((element.nodeType == element.ELEMENT_NODE &&
+ element.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") ||
+ ChromeUtils.getClassName(element) === "HTMLEmbedElement" ||
+ ChromeUtils.getClassName(element) === "HTMLObjectElement" ||
+ ChromeUtils.getClassName(element) === "HTMLSelectElement" ||
+ ChromeUtils.getClassName(element) === "HTMLTextAreaElement")
+ return true;
+
+ // Don't start a find if the focus is on an editable field
+ if (ChromeUtils.getClassName(element) === "HTMLInputElement" &&
+ element.mozIsTextField(false))
+ return true;
+
+ // Don't start a find if the focus isn't or can't be set to content
+ var w = window.document.commandDispatcher.focusedWindow;
+ if (w.top == window)
+ w = window.content;
+ if (!w)
+ return true;
+
+ var webNav = w.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation);
+ try {
+ // Don't start a find if the window is in design mode
+ if (webNav.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession)
+ .windowIsEditable(w))
+ return true;
+ } catch (e) {
+ }
+
+ switch (aEvent.charCode) {
+ // Start finding text as you type
+ case kSlash:
+ aEvent.preventDefault();
+ this.startFind(window, false);
+ break;
+
+ // Start finding links as you type
+ case kApostrophe:
+ aEvent.preventDefault();
+ this.startFind(window, true);
+ break;
+
+ default:
+ // Don't start if typeahead find is disabled
+ if (!this.mPrefs.getBoolPref("autostart"))
+ return true;
+ // Don't start in windows that don't want autostart
+ if (webNav.QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler.getAttribute("autofind") == "false")
+ return true;
+ this.startFind(window, this.mPrefs.getBoolPref("linksonly"));
+ this.processKey(aEvent);
+ }
+ return false;
+ },
+
+ /* nsISelectionListener */
+ notifySelectionChanged: function(aDoc, aSelection, aReason) {
+ this.stopFind(false);
+ },
+
+ /* private methods */
+ showStatus: function(aText) {
+ if (this.mXULBrowserWindow)
+ this.mXULBrowserWindow.setOverLink(aText, null);
+ },
+ showStatusString: function(aString) {
+ // Set the status text from a localised string
+ this.showStatus(aString && this.mBundle.GetStringFromName(aString));
+ },
+ showStatusMatch: function(aResult, aExtra) {
+ // Set the status text from a find result
+ // link|text "..." [not] found [next|previous match] [(href)]
+ if (aExtra)
+ aExtra = " " + this.mBundle.GetStringFromName(aExtra);
+ var url = "";
+ var string = this.mLinks ? "link" : "text";
+ if (aResult == Ci.nsITypeAheadFind.FIND_NOTFOUND)
+ string += "not";
+ else if (this.mFind.foundLink && this.mFind.foundLink.href)
+ url = " " + this.mBundle.GetStringFromName("openparen") +
+ this.mFind.foundLink.href +
+ this.mBundle.GetStringFromName("closeparen");
+ string += "found";
+ this.showStatus(this.mBundle.GetStringFromName(string) +
+ this.mSearchString +
+ this.mBundle.GetStringFromName("closequote") +
+ aExtra + url);
+ },
+ startTimer: function() {
+ if (this.mPrefs.getBoolPref("enabletimeout")) {
+ if (!this.mTimer)
+ this.mTimer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+ this.mTimer.initWithCallback(this,
+ this.mPrefs.getIntPref("timeout"),
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+ },
+ processKey: function(aEvent) {
+ // Escape always cancels the find.
+ if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ this.stopFind(false);
+ return false;
+ }
+
+ var result = Ci.nsITypeAheadFind.FIND_NOTFOUND;
+ if (aEvent.keyCode == aEvent.DOM_VK_BACK_SPACE) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ this.mSearchString = this.mSearchString.slice(0, -1);
+ // Backspacing past the start of the string cancels the find.
+ if (!this.mSearchString) {
+ this.stopFind(true);
+ return false;
+ }
+ this.startTimer();
+ // The find will change the selection, so stop listening for changes
+ this.mEventTarget.removeEventListener("blur", this, true);
+ if (this.mSelection)
+ this.mSelection.removeSelectionListener(this);
+ // Don't bother finding until we get back to a working string
+ if (!this.mBadKeysSinceMatch || !--this.mBadKeysSinceMatch)
+ result = this.mFind.find(this.mSearchString, this.mLinks);
+ } else {
+ // Ignore control characters.
+ if (aEvent.keyCode || aEvent.charCode < kSpace)
+ return true;
+
+ this.startTimer();
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+
+ // It looks as if the cat walked on the keyboard.
+ if (this.mBadKeysSinceMatch >= 3)
+ return false;
+
+ // The find will change the selection/focus, so stop listening for changes
+ this.mEventTarget.removeEventListener("blur", this, true);
+ if (this.mSelection)
+ this.mSelection.removeSelectionListener(this);
+ var previousString = this.mSearchString;
+ this.mSearchString += String.fromCharCode(aEvent.charCode).toLowerCase();
+ if (!this.mBadKeysSinceMatch) {
+ result = this.mFind.find(this.mSearchString, this.mLinks);
+ if (previousString &&
+ result == Ci.nsITypeAheadFind.FIND_NOTFOUND)
+ // Use a separate find instance to rehighlight the previous match
+ // until bug 463294 is fixed.
+ this.mFound.find(previousString, this.mLinks);
+ }
+ if (result == Ci.nsITypeAheadFind.FIND_NOTFOUND)
+ this.mBadKeysSinceMatch++;
+ }
+
+ // Ensure that the correct frame is focused (work around for bug 485213).
+ if (this.mFind.currentWindow)
+ this.mFind.currentWindow.focus();
+
+ this.showStatusMatch(result, "");
+ if (!this.mFindService)
+ this.mFindService = Cc["@mozilla.org/find/find_service;1"]
+ .getService(Ci.nsIFindService);
+ this.mFindService.searchString = this.mSearchString;
+ // Watch for blur changes in case the cursor leaves the current field.
+ this.mEventTarget.addEventListener("blur", this, true);
+ // Also watch for the cursor moving within the current field or window.
+ var commandDispatcher = this.mEventTarget.ownerDocument.commandDispatcher;
+ var editable = commandDispatcher.focusedElement;
+ if (editable &&
+ ["HTMLInputElement", "HTMLTextAreaElement"]
+ .includes(ChromeUtils.getClassName(editable)))
+ this.mSelection = editable.editor.selection;
+ else
+ this.mSelection = commandDispatcher.focusedWindow.getSelection();
+ this.mSelection.addSelectionListener(this);
+ return false;
+ },
+ startFind: function(aWindow, aLinks) {
+ if (this.mEventTarget)
+ this.stopFind(true);
+ // Try to get the status bar for the specified window
+ this.mXULBrowserWindow =
+ aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow)
+ .XULBrowserWindow;
+
+ // If the current window is chrome then focus content instead
+ var w = aWindow.document.commandDispatcher.focusedWindow.top;
+ if (w == aWindow)
+ (w = aWindow.content).focus();
+
+ // Get two toolkit typeaheadfind instances if we don't have them already.
+ var docShell = w.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ if (!this.mFind) {
+ this.mFind = Cc["@mozilla.org/typeaheadfind;1"]
+ .createInstance(Ci.nsITypeAheadFind);
+ this.mFind.init(docShell);
+ this.mFound = Cc["@mozilla.org/typeaheadfind;1"]
+ .createInstance(Ci.nsITypeAheadFind);
+ this.mFound.init(docShell);
+ }
+
+ // Get the string bundle if we don't have it already.
+ if (!this.mBundle)
+ this.mBundle = Services.strings
+ .createBundle("chrome://communicator/locale/typeaheadfind.properties");
+
+ // Set up all our properties
+ this.mFind.setDocShell(docShell);
+ this.mFound.setDocShell(docShell);
+ this.mEventTarget = docShell.chromeEventHandler;
+ this.mEventTarget.addEventListener("keypress", this, true);
+ this.mEventTarget.addEventListener("keydown", this, true);
+ this.mEventTarget.addEventListener("keyup", this, true);
+ this.mEventTarget.addEventListener("pagehide", this, true);
+ this.mCurrentWindow = w;
+ this.mBadKeysSinceMatch = 0;
+ this.mSearchString = "";
+ this.mLinks = aLinks;
+ this.showStatusString(this.mLinks ? "startlinkfind" : "starttextfind");
+ this.startTimer();
+ },
+ stopFind: function(aClear) {
+ if (this.mTimer)
+ this.mTimer.cancel();
+ if (this.mFind)
+ this.mFind.setSelectionModeAndRepaint(
+ Ci.nsISelectionController.SELECTION_ON);
+ if (this.mEventTarget) {
+ this.mEventTarget.removeEventListener("blur", this, true);
+ this.mEventTarget.removeEventListener("pagehide", this, true);
+ this.mEventTarget.removeEventListener("keypress", this, true);
+ this.mEventTarget.removeEventListener("keydown", this, true);
+ this.mEventTarget.removeEventListener("keyup", this, true);
+ }
+ this.mEventTarget = null;
+ if (this.mSelection)
+ this.mSelection.removeSelectionListener(this);
+ this.mSelection = null;
+ this.showStatusString(aClear ? "" : "stopfind");
+ if (aClear)
+ this.mSearchString = "";
+ if (aClear && this.mFind)
+ this.mFind.collapseSelection();
+ },
+};
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([typeAheadFind]);
diff --git a/comm/suite/browser/pageinfo/feeds.js b/comm/suite/browser/pageinfo/feeds.js
new file mode 100644
index 0000000000..194f436d23
--- /dev/null
+++ b/comm/suite/browser/pageinfo/feeds.js
@@ -0,0 +1,31 @@
+/* -*- 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/. */
+
+function initFeedTab(feeds)
+{
+ for (let feed of feeds) {
+ let [name, type, url] = feed;
+ addRow(name, type, url);
+ }
+
+ var feedListbox = document.getElementById("feedListbox");
+ document.getElementById("feedTab").hidden = feedListbox.getRowCount() == 0;
+}
+
+function onSubscribeFeed(event)
+{
+ var listbox = document.getElementById("feedListbox");
+ subscribeToFeed(listbox.selectedItem.getAttribute("feedURL"), event);
+}
+
+function addRow(name, type, url)
+{
+ var item = document.createElement("richlistitem");
+ item.setAttribute("feed", "true");
+ item.setAttribute("name", name);
+ item.setAttribute("type", type);
+ item.setAttribute("feedURL", url);
+ document.getElementById("feedListbox").appendChild(item);
+}
diff --git a/comm/suite/browser/pageinfo/feeds.xml b/comm/suite/browser/pageinfo/feeds.xml
new file mode 100644
index 0000000000..c487e30f55
--- /dev/null
+++ b/comm/suite/browser/pageinfo/feeds.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?><!-- -*- Mode: nXML; indent-tabs-mode: nil -*- -->
+<!-- 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/. -->
+
+<!DOCTYPE bindings [
+ <!ENTITY % pageInfoDTD SYSTEM "chrome://navigator/locale/pageInfo.dtd">
+ %pageInfoDTD;
+]>
+
+<bindings id="feedBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="feed" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:vbox flex="1">
+ <xul:hbox flex="1">
+ <xul:textbox flex="1" readonly="true" xbl:inherits="value=name"
+ class="feedTitle"/>
+ <xul:label xbl:inherits="value=type"/>
+ </xul:hbox>
+ <xul:vbox align="start" flex="1">
+ <xul:hbox>
+ <xul:label xbl:inherits="value=feedURL,tooltiptext=feedURL"
+ class="text-link"
+ flex="1"
+ onclick="openUILink(this.value, event);" crop="end"/>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:hbox flex="1" class="feed-subscribe">
+ <xul:spacer flex="1"/>
+ <xul:button label="&feedSubscribe;" accesskey="&feedSubscribe.accesskey;"
+ oncommand="onSubscribeFeed(event)"/>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ </binding>
+</bindings>
diff --git a/comm/suite/browser/pageinfo/pageInfo.css b/comm/suite/browser/pageinfo/pageInfo.css
new file mode 100644
index 0000000000..749c01a434
--- /dev/null
+++ b/comm/suite/browser/pageinfo/pageInfo.css
@@ -0,0 +1,18 @@
+/* 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/. */
+
+richlistitem[feed] {
+ -moz-binding: url("chrome://navigator/content/pageinfo/feeds.xml#feed");
+}
+
+#thepreviewimage {
+ display: block;
+/* This following entry can be removed when Bug 522850 is fixed. */
+ min-width: 1px;
+}
+
+.urltext:-moz-locale-dir(rtl) {
+ direction: ltr !important;
+ text-align: end !important;
+}
diff --git a/comm/suite/browser/pageinfo/pageInfo.js b/comm/suite/browser/pageinfo/pageInfo.js
new file mode 100644
index 0000000000..d4f59be55c
--- /dev/null
+++ b/comm/suite/browser/pageinfo/pageInfo.js
@@ -0,0 +1,1177 @@
+/* -*- 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 {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ Downloads: "resource://gre/modules/Downloads.jsm",
+ FileUtils: "resource://gre/modules/FileUtils.jsm",
+});
+
+//******** define a js object to implement nsITreeView
+function pageInfoTreeView(treeid, copycol)
+{
+ /* copycol is the index number for the column that we want to add to
+ * the copy-n-paste buffer when the user hits accel-c.
+ */
+ this.treeid = treeid;
+ this.copycol = copycol;
+ this.rows = 0;
+ this.tree = null;
+ this.data = [ ];
+ this.selection = null;
+ this.sortcol = -1;
+ this.sortdir = false;
+}
+
+pageInfoTreeView.prototype = {
+ get rowCount() { return this.rows; },
+
+ setTree: function(tree)
+ {
+ this.tree = tree;
+ },
+
+ getCellText: function(row, column)
+ {
+ // row can be null, but js arrays are 0-indexed.
+ return this.data[row][column.index] || "";
+ },
+
+ setCellValue: function(row, column, value)
+ {
+ },
+
+ setCellText: function(row, column, value)
+ {
+ this.data[row][column.index] = value;
+ },
+
+ addRow: function(row)
+ {
+ this.rows = this.data.push(row);
+ this.rowCountChanged(this.rows - 1, 1);
+ if (this.selection.count == 0 && this.rowCount && !gImageElement) {
+ this.selection.select(0);
+ }
+ },
+
+ addRows: function(rows)
+ {
+ for (let row of rows) {
+ this.addRow(row);
+ }
+ },
+
+ rowCountChanged: function(index, count)
+ {
+ this.tree.rowCountChanged(index, count);
+ },
+
+ invalidate: function()
+ {
+ this.tree.invalidate();
+ },
+
+ clear: function()
+ {
+ if (this.tree)
+ this.tree.rowCountChanged(0, -this.rows);
+ this.rows = 0;
+ this.data = [];
+ },
+
+ cycleHeader: function cycleHeader(col)
+ {
+ this.doSort(col, col.index);
+ },
+
+ doSort: function doSort(col, index, comparator)
+ {
+ var tree = document.getElementById(this.treeid);
+ if (!comparator) {
+ comparator = function comparator(a, b) {
+ return (a || "").toLowerCase().localeCompare((b || "").toLowerCase());
+ };
+ }
+
+ this.sortdir = gTreeUtils.sort(tree, this, this.data, index,
+ comparator, this.sortcol, this.sortdir);
+
+ Array.from(this.tree.columns).forEach(function(treecol) {
+ treecol.element.removeAttribute("sortActive");
+ treecol.element.removeAttribute("sortDirection");
+ });
+ col.element.setAttribute("sortActive", true);
+ col.element.setAttribute("sortDirection", this.sortdir ?
+ "ascending" : "descending");
+
+ this.sortcol = index;
+ },
+
+ getRowProperties: function(row) { return ""; },
+ getCellProperties: function(row, column) { return ""; },
+ getColumnProperties: function(column) { return ""; },
+ isContainer: function(index) { return false; },
+ isContainerOpen: function(index) { return false; },
+ isSeparator: function(index) { return false; },
+ isSorted: function() { return this.sortcol > -1 },
+ canDrop: function(index, orientation) { return false; },
+ drop: function(row, orientation) { return false; },
+ getParentIndex: function(index) { return -1; },
+ hasNextSibling: function(index, after) { return false; },
+ getLevel: function(index) { return 0; },
+ getImageSrc: function(row, column) { },
+ getProgressMode: function(row, column) { },
+ getCellValue: function(row, column) {
+ let col = (column != null) ? column : this.copycol;
+ return (row < 0 || col < 0) ? "" : (this.data[row][col] || "");
+ },
+ toggleOpenState: function(index) { },
+ selectionChanged: function() { },
+ cycleCell: function(row, column) { },
+ isEditable: function(row, column) { return false; },
+ isSelectable: function(row, column) { return false; },
+};
+
+// mmm, yummy. global variables.
+var gDocInfo = null;
+var gImageElement = null;
+
+// column number to help using the data array
+const COL_IMAGE_ADDRESS = 0;
+const COL_IMAGE_TYPE = 1;
+const COL_IMAGE_SIZE = 2;
+const COL_IMAGE_ALT = 3;
+const COL_IMAGE_COUNT = 4;
+const COL_IMAGE_NODE = 5;
+const COL_IMAGE_BG = 6;
+const COL_IMAGE_SIZENUM = 7;
+const COL_IMAGE_PERSIST = 8;
+const COL_IMAGE_MIME = 9;
+
+// column number to copy from, second argument to pageInfoTreeView's constructor
+const COPYCOL_NONE = -1;
+const COPYCOL_META_CONTENT = 1;
+const COPYCOL_FORM_ACTION = 2;
+const COPYCOL_FIELD_VALUE = 3;
+const COPYCOL_LINK_ADDRESS = 1;
+const COPYCOL_IMAGE = COL_IMAGE_ADDRESS;
+
+// one nsITreeView for each tree in the window
+var gMetaView = new pageInfoTreeView("metatree", COPYCOL_META_CONTENT);
+var gFormView = new pageInfoTreeView("formtree", COPYCOL_FORM_ACTION);
+var gFieldView = new pageInfoTreeView("formpreview", COPYCOL_FIELD_VALUE);
+var gLinkView = new pageInfoTreeView("linktree", COPYCOL_LINK_ADDRESS);
+var gImageView = new pageInfoTreeView("imagetree", COPYCOL_IMAGE);
+
+gImageView.getCellProperties = function(row, col) {
+ var data = gImageView.data[row];
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var properties = col.id == "image-address" ? "ltr" : "";
+ if (!checkProtocol(data) || item.HTMLEmbedElement ||
+ (item.HTMLObjectElement && !item.type.startsWith("image/")))
+ properties += " broken";
+
+ return properties;
+};
+
+gFormView.getCellProperties = function(row, col) {
+ return col.id == "form-action" ? "ltr" : "";
+};
+
+gLinkView.getCellProperties = function(row, col) {
+ return col.id == "link-address" ? "ltr" : "";
+};
+
+gImageView.cycleHeader = function(col)
+{
+ var index = col.index;
+ var comparator;
+ switch (col.index) {
+ case COL_IMAGE_SIZE:
+ index = COL_IMAGE_SIZENUM;
+ case COL_IMAGE_COUNT:
+ comparator = function numComparator(a, b) { return a - b; };
+ break;
+ }
+
+ this.doSort(col, index, comparator);
+};
+
+var gImageHash = { };
+
+// localized strings (will be filled in when the document is loaded)
+// this isn't all of them, these are just the ones that would otherwise have been loaded inside a loop
+var gStrings = { };
+var gBundle;
+
+const DRAGSERVICE_CONTRACTID = "@mozilla.org/widget/dragservice;1";
+const TRANSFERABLE_CONTRACTID = "@mozilla.org/widget/transferable;1";
+const STRING_CONTRACTID = "@mozilla.org/supports-string;1";
+
+var loadContextInfo = Services.loadContextInfo.fromLoadContext(
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext), false);
+var diskStorage = Services.cache2.diskCacheStorage(loadContextInfo, false);
+
+const nsICertificateDialogs = Ci.nsICertificateDialogs;
+const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1"
+
+/* Overlays register functions here.
+ * These arrays are used to hold callbacks that Page Info will call at
+ * various stages. Use them by simply appending a function to them.
+ * For example, add a function to onLoadRegistry by invoking
+ * "onLoadRegistry.push(XXXLoadFunc);"
+ * The XXXLoadFunc should be unique to the overlay module, and will be
+ * invoked as "XXXLoadFunc();"
+ */
+
+// These functions are called to build the data displayed in the Page
+// Info window.
+var onLoadRegistry = [ ];
+
+// These functions are called to remove old data still displayed in
+// the window when the document whose information is displayed
+// changes. For example, the list of images in the Media tab
+// is cleared.
+var onResetRegistry = [ ];
+
+// These functions are called once when all the elements in all of the target
+// document (and all of its subframes, if any) have been processed
+var onFinished = [ ];
+
+// These functions are called once when the Page Info window is closed.
+var onUnloadRegistry = [ ];
+
+/* Called when PageInfo window is loaded. Arguments are:
+ * window.arguments[0] - (optional) an object consisting of
+ * - doc: (optional) document to use for source. if not provided,
+ * the calling window's document will be used
+ * - initialTab: (optional) id of the inital tab to display
+ */
+function onLoadPageInfo()
+{
+ gBundle = document.getElementById("pageinfobundle");
+ var strNames = ["unknown", "notSet", "mediaImg", "mediaBGImg",
+ "mediaBorderImg", "mediaListImg", "mediaCursor",
+ "mediaObject", "mediaEmbed", "mediaLink", "mediaInput",
+ "mediaVideo", "mediaAudio",
+ "formTitle", "formUntitled", "formDefaultTarget",
+ "formChecked", "formUnchecked", "formPassword", "linkAnchor",
+ "linkArea", "linkSubmission", "linkSubmit", "linkRel",
+ "linkStylesheet", "linkRev", "linkX", "linkScript",
+ "linkScriptInline", "yes"];
+ strNames.forEach(function(n) { gStrings[n] = gBundle.getString(n); });
+
+ var args = "arguments" in window &&
+ window.arguments.length >= 1 &&
+ window.arguments[0];
+
+ // init views
+ function initView(treeid, view)
+ {
+ document.getElementById(treeid).view = view;
+ }
+
+ initView("imagetree", gImageView);
+ initView("formtree", gFormView);
+ initView("formpreview", gFieldView);
+ initView("linktree", gLinkView);
+ initPermission();
+
+ /* Select the requested tab, if the name is specified */
+ loadTab(args);
+ Services.obs.notifyObservers(window, "page-info-dialog-loaded");
+}
+
+function loadPageInfo(frameOuterWindowID, imageElement, browser)
+{
+ browser = browser || window.opener.gBrowser.selectedBrowser;
+ let mm = browser.messageManager;
+
+ gStrings["application/rss+xml"] = gBundle.getString("feedRss");
+ gStrings["application/atom+xml"] = gBundle.getString("feedAtom");
+ gStrings["text/xml"] = gBundle.getString("feedXML");
+ gStrings["application/xml"] = gBundle.getString("feedXML");
+ gStrings["application/rdf+xml"] = gBundle.getString("feedXML");
+
+ // Look for pageInfoListener in content.js.
+ // Sends message to listener with arguments.
+ mm.sendAsyncMessage("PageInfo:getData", {strings: gStrings,
+ frameOuterWindowID: frameOuterWindowID},
+ { imageElement });
+
+ let pageInfoData;
+
+ // Get initial pageInfoData needed to display the general, feeds, permission
+ // and security tabs.
+ mm.addMessageListener("PageInfo:data", function onmessage(message) {
+ mm.removeMessageListener("PageInfo:data", onmessage);
+ pageInfoData = message.data;
+ let docInfo = pageInfoData.docInfo;
+ let windowInfo = pageInfoData.windowInfo;
+ let uri = makeURI(docInfo.documentURIObject.spec);
+ let principal = docInfo.principal;
+ gDocInfo = docInfo;
+
+ gImageElement = pageInfoData.imageInfo;
+
+ var titleFormat = windowInfo.isTopWindow ? "pageInfo.page.title"
+ : "pageInfo.frame.title";
+ document.title = gBundle.getFormattedString(titleFormat,
+ [docInfo.location]);
+
+ document.getElementById("main-window").setAttribute("relatedUrl",
+ docInfo.location);
+
+ makeGeneralTab(pageInfoData.metaViewRows, docInfo);
+ initFeedTab(pageInfoData.feeds);
+ onLoadPermission(uri, principal);
+ securityOnLoad(uri, windowInfo);
+ });
+
+ // Get the media elements from content script to setup the media tab.
+ mm.addMessageListener("PageInfo:mediaData", function onmessage(message) {
+ // Page info window was closed.
+ if (window.closed) {
+ mm.removeMessageListener("PageInfo:mediaData", onmessage);
+ return;
+ }
+
+ // The page info media fetching has been completed.
+ if (message.data.isComplete) {
+ mm.removeMessageListener("PageInfo:mediaData", onmessage);
+ onFinished.forEach(function(func) { func(pageInfoData); });
+ return;
+ }
+
+ if (message.data.imageItems) {
+ for (let item of message.data.imageItems) {
+ addImage(item);
+ }
+ selectImage();
+ }
+
+ if (message.data.linkItems) {
+ gLinkView.addRows(message.data.linkItems);
+ }
+
+ if (message.data.formItems) {
+ gFormView.addRows(message.data.formItems);
+ }
+ });
+
+ /* Call registered overlay init functions */
+ onLoadRegistry.forEach(function(func) { func(); });
+}
+
+function resetPageInfo(args)
+{
+ /* Reset Media tab */
+ // Remove the observer, only if there is at least 1 image.
+ if (gImageView.data.length != 0) {
+ Services.obs.removeObserver(imagePermissionObserver, "perm-changed");
+ }
+
+ /* Reset tree views */
+ gMetaView.clear();
+ gFormView.clear();
+ gFieldView.clear();
+ gLinkView.clear();
+ gImageView.clear();
+ gImageHash = {};
+
+ /* Reset Feeds Tab */
+ var feedListbox = document.getElementById("feedListbox");
+ while (feedListbox.hasChildNodes())
+ feedListbox.lastChild.remove();
+
+ /* Call registered overlay reset functions */
+ onResetRegistry.forEach(function(func) { func(); });
+
+ /* Rebuild the data */
+ loadTab(args);
+
+ Services.obs.notifyObservers(window, "page-info-dialog-reset");
+}
+
+function onUnloadPageInfo()
+{
+ // Remove the observer, only if there is at least 1 image.
+ if (gImageView.data.length != 0) {
+ Services.obs.removeObserver(imagePermissionObserver, "perm-changed");
+ }
+
+ /* Call registered overlay unload functions */
+ onUnloadRegistry.forEach(function(func) { func(); });
+}
+
+function doHelpButton()
+{
+ const helpTopics = {
+ "generalTab": "pageinfo_general",
+ "mediaTab": "pageinfo_media",
+ // "feedTab": "pageinfo_feed",
+ // "permTab": "pageinfo_permissions",
+ "formsTab": "pageinfo_forms",
+ "linksTab": "pageinfo_links",
+ "securityTab": "pageinfo_security"
+ };
+
+ var tabbox = document.getElementById("tabbox");
+ var helpdoc = helpTopics[tabbox.selectedTab.id] || "nav-page-info";
+ openHelp(helpdoc, "chrome://communicator/locale/help/suitehelp.rdf");
+}
+
+function showTab(id)
+{
+ var tabbox = document.getElementById("tabbox");
+ var selectedTab = document.getElementById(id) ||
+ document.getElementById(id + "Tab") || // Firefox compatibility sillyness
+ document.getElementById("generalTab");
+ tabbox.selectedTab = selectedTab;
+ selectedTab.focus();
+}
+
+function loadTab(args)
+{
+ // If the "View Image Info" context menu item was used, the related image
+ // element is provided as an argument. This can't be a background image.
+ let imageElement = args && args.imageElement;
+ let frameOuterWindowID = args && args.frameOuterWindowID;
+ let browser = args && args.browser;
+
+ /* Load the page info */
+ loadPageInfo(frameOuterWindowID, imageElement, browser);
+
+ /* Select the requested tab, if the name is specified */
+ var initialTab = (args && args.initialTab) || "generalTab";
+ showTab(initialTab);
+}
+
+function onClickMore()
+{
+ showTab("securityTab");
+}
+
+function openCacheEntry(key, cb)
+{
+ var checkCacheListener = {
+ onCacheEntryCheck: function(entry, appCache) {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+ onCacheEntryAvailable: function(entry, isNew, appCache, status) {
+ cb(entry);
+ }
+ };
+ diskStorage.asyncOpenURI(Services.io.newURI(key, null, null), "",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ checkCacheListener);
+}
+
+function makeGeneralTab(metaViewRows, docInfo)
+{
+ var title = (docInfo.title) ? docInfo.title : gBundle.getString("noPageTitle");
+ document.getElementById("titletext").value = title;
+
+ var url = docInfo.location.toString();
+ setItemValue("urltext", url);
+
+ var referrer = ("referrer" in docInfo && docInfo.referrer);
+ setItemValue("refertext", referrer);
+
+ var mode = ("compatMode" in docInfo && docInfo.compatMode == "BackCompat") ? "generalQuirksMode" : "generalStrictMode";
+ document.getElementById("modetext").value = gBundle.getString(mode);
+
+ // find out the mime type
+ var mimeType = docInfo.contentType;
+ setItemValue("typetext", mimeType);
+
+ // get the document characterset
+ var encoding = docInfo.characterSet;
+ document.getElementById("encodingtext").value = encoding;
+
+ var length = metaViewRows.length;
+
+ var metaGroup = document.getElementById("metaTags");
+ if (!length) {
+ metaGroup.collapsed = true;
+ }
+ else {
+ var metaTagsCaption = document.getElementById("metaTagsCaption");
+ if (length == 1)
+ metaTagsCaption.label = gBundle.getString("generalMetaTag");
+ else
+ metaTagsCaption.label = gBundle.getFormattedString("generalMetaTags", [length]);
+ var metaTree = document.getElementById("metatree");
+ metaTree.view = gMetaView;
+
+ // Add the metaViewRows onto the general tab's meta info tree.
+ gMetaView.addRows(metaViewRows);
+
+ metaGroup.collapsed = false;
+ }
+
+ // get the date of last modification
+ var modifiedText = formatDate(docInfo.lastModified, gStrings.notSet);
+ document.getElementById("modifiedtext").value = modifiedText;
+
+ // get cache info
+ var cacheKey = url.replace(/#.*$/, "");
+ openCacheEntry(cacheKey, function(cacheEntry) {
+ var sizeText;
+ if (cacheEntry) {
+ var pageSize = cacheEntry.dataSize;
+ var kbSize = formatNumber(Math.round(pageSize / 1024 * 100) / 100);
+ sizeText = gBundle.getFormattedString("generalSize", [kbSize, formatNumber(pageSize)]);
+ }
+ setItemValue("sizetext", sizeText);
+ });
+}
+
+function ensureSelection(view)
+{
+ // only select something if nothing is currently selected
+ // and if there's anything to select
+ if (view.selection && view.selection.count == 0 && view.rowCount)
+ view.selection.select(0);
+}
+
+function addImage(imageViewRow)
+{
+ let [url, type, alt, elem, isBg] = imageViewRow;
+
+ if (!url)
+ return;
+
+ if (!gImageHash.hasOwnProperty(url))
+ gImageHash[url] = { };
+ if (!gImageHash[url].hasOwnProperty(type))
+ gImageHash[url][type] = { };
+ if (!gImageHash[url][type].hasOwnProperty(alt)) {
+ gImageHash[url][type][alt] = gImageView.data.length;
+ var row = [url, type, gStrings.unknown, alt, 1, elem, isBg, -1, null, null];
+ gImageView.addRow(row);
+
+ // Fill in cache data asynchronously
+ openCacheEntry(url, function(cacheEntry) {
+ if (cacheEntry) {
+ // Update the corresponding data entries from the cache.
+ var imageSize = cacheEntry.dataSize;
+ // If it is not -1 then replace with actual value, else keep as unknown.
+ if (imageSize && imageSize != -1) {
+ var kbSize = Math.round(imageSize / 1024 * 100) / 100;
+ row[2] = gBundle.getFormattedString("mediaFileSize",
+ [formatNumber(kbSize)]);
+ row[7] = imageSize;
+ }
+ row[8] = cacheEntry.persistent;
+ row[9] = getContentTypeFromHeaders(cacheEntry);
+ // Invalidate the row to trigger a repaint.
+ gImageView.tree.invalidateRow(gImageView.data.indexOf(row));
+ }
+ });
+
+ // Add the observer, only once.
+ if (gImageView.data.length == 1) {
+ Services.obs.addObserver(imagePermissionObserver, "perm-changed");
+ }
+ }
+ else {
+ var i = gImageHash[url][type][alt];
+ gImageView.data[i][COL_IMAGE_COUNT]++;
+ // The same image can occur several times on the page at different sizes.
+ // If the "View Image Info" context menu item was used, ensure we select
+ // the correct element.
+ if (!gImageView.data[i][COL_IMAGE_BG] &&
+ gImageElement && url == gImageElement.currentSrc &&
+ gImageElement.width == elem.width &&
+ gImageElement.height == elem.height &&
+ gImageElement.imageText == elem.imageText) {
+ gImageView.data[i][COL_IMAGE_NODE] = elem;
+ }
+ }
+}
+
+//******** Form Stuff
+function onFormSelect()
+{
+ if (gFormView.selection.count == 1)
+ {
+ var formPreview = document.getElementById("formpreview");
+ gFieldView.clear();
+ formPreview.view = gFieldView;
+
+ var clickedRow = gFormView.selection.currentIndex;
+ // form-node;
+ var form = gFormView.data[clickedRow][3];
+
+ var ft = null;
+ if (form.name)
+ ft = gBundle.getFormattedString("formTitle", [form.name]);
+
+ setItemValue("formenctype", form.encoding, gStrings.default);
+ setItemValue("formtarget", form.target, gStrings.formDefaultTarget);
+ document.getElementById("formname").value = ft || gStrings.formUntitled;
+
+ gFieldView.addRows(form.formfields);
+ }
+}
+
+//******** Link Stuff
+function onBeginLinkDrag(event,urlField,descField)
+{
+ if (event.originalTarget.localName != "treechildren")
+ return;
+
+ var tree = event.target;
+ if (!("treeBoxObject" in tree))
+ tree = tree.parentNode;
+
+ var row = tree.treeBoxObject.getRowAt(event.clientX, event.clientY);
+ if (row == -1)
+ return;
+
+ // Adding URL flavor
+ var col = tree.columns[urlField];
+ var url = tree.view.getCellText(row, col);
+ col = tree.columns[descField];
+ var desc = tree.view.getCellText(row, col);
+
+ var dataTransfer = event.dataTransfer;
+ dataTransfer.setData("text/x-moz-url", url + "\n" + desc);
+ dataTransfer.setData("text/url-list", url);
+ dataTransfer.setData("text/plain", url);
+}
+
+//******** Image Stuff
+function getSelectedRows(tree) {
+ var start = { };
+ var end = { };
+ var numRanges = tree.view.selection.getRangeCount();
+
+ var rowArray = [ ];
+ for (var t = 0; t < numRanges; t++) {
+ tree.view.selection.getRangeAt(t, start, end);
+ for (var v = start.value; v <= end.value; v++)
+ rowArray.push(v);
+ }
+
+ return rowArray;
+}
+
+function getSelectedRow(tree) {
+ var rows = getSelectedRows(tree);
+ return (rows.length == 1) ? rows[0] : -1;
+}
+
+function selectSaveFolder(aCallback) {
+ return selectSaveFolderTask(aCallback).catch(Cu.reportError);
+}
+
+async function selectSaveFolderTask(aCallback) {
+ let titleText = gBundle.getString("mediaSelectFolder");
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+
+ fp.init(window, titleText, Ci.nsIFilePicker.modeGetFolder);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ try {
+ let initialDir = Services.prefs.getComplexValue("browser.download.dir",
+ Ci.nsIFile);
+ if (!initialDir) {
+ let downloadsDir = await Downloads.getSystemDownloadsDirectory();
+ initialDir = new FileUtils.File(downloadsDir);
+ }
+
+ fp.displayDirectory = initialDir;
+ } catch (ex) {
+ }
+
+ let result = await new Promise(resolve => fp.open(resolve));
+
+ if (result == Ci.nsIFilePicker.returnOK) {
+ aCallback(fp.file.QueryInterface(Ci.nsIFile));
+ } else {
+ aCallback(null);
+ }
+}
+
+function saveMedia()
+{
+ var tree = document.getElementById("imagetree");
+ var rowArray = getSelectedRows(tree);
+ if (rowArray.length == 1) {
+ let row = rowArray[0];
+ let item = gImageView.data[row][COL_IMAGE_NODE];
+ let url = gImageView.data[row][COL_IMAGE_ADDRESS];
+
+ if (url) {
+ let titleKey = "SaveImageTitle";
+
+ if (item instanceof HTMLVideoElement)
+ titleKey = "SaveVideoTitle";
+ else if (item instanceof HTMLAudioElement)
+ titleKey = "SaveAudioTitle";
+
+ saveURL(url, null, titleKey, false, false, makeURI(item.baseURI),
+ null, gDocInfo.isContentWindowPrivate,
+ gDocument.nodePrincipal);
+ }
+ } else {
+ selectSaveFolder(function(aDirectory) {
+ if (aDirectory) {
+ var saveAnImage = function(aURIString, aChosenData, aBaseURI) {
+ uniqueFile(aChosenData.file);
+ internalSave(aURIString, null, null, null, null, false,
+ "SaveImageTitle", aChosenData, aBaseURI, null, false,
+ null, gDocInfo.isContentWindowPrivate,
+ gDocument.nodePrincipal);
+ };
+
+ for (var i = 0; i < rowArray.length; i++) {
+ let v = rowArray[i];
+ let dir = aDirectory.clone();
+ let item = gImageView.data[v][COL_IMAGE_NODE];
+ let uriString = gImageView.data[v][COL_IMAGE_ADDRESS];
+ let uri = makeURI(uriString);
+
+ try {
+ uri.QueryInterface(Ci.nsIURL);
+ dir.append(decodeURIComponent(uri.fileName));
+ } catch (ex) {
+ // data:/blob: uris
+ // Supply a dummy filename, otherwise Download Manager
+ // will try to delete the base directory on failure.
+ dir.append(gImageView.data[v][COL_IMAGE_TYPE]);
+ }
+
+ if (i == 0) {
+ saveAnImage(uriString, new AutoChosen(dir, uri), makeURI(item.baseURI));
+ } else {
+ // This delay is a hack which prevents the download manager
+ // from opening many times. See bug 377339.
+ setTimeout(saveAnImage, 200, uriString, new AutoChosen(dir, uri),
+ makeURI(item.baseURI));
+ }
+ }
+ }
+ });
+ }
+}
+
+function onBlockImage(aChecked)
+{
+ var uri = makeURI(document.getElementById("imageurltext").value);
+ if (aChecked)
+ Services.perms.add(uri, "image", Services.perms.DENY_ACTION);
+ else
+ Services.perms.remove(uri, "image");
+}
+
+function onImageSelect()
+{
+ var previewBox = document.getElementById("mediaPreviewBox");
+ var mediaSaveBox = document.getElementById("mediaSaveBox");
+ var mediaSaveButton = document.getElementById("imagesaveasbutton");
+ var splitter = document.getElementById("mediaSplitter");
+ var tree = document.getElementById("imagetree");
+ var count = tree.view.selection.count;
+
+ if (count == 0)
+ {
+ previewBox.collapsed = true;
+ mediaSaveBox.collapsed = true;
+ mediaSaveButton.disabled = true;
+ splitter.collapsed = true;
+ tree.flex = 1;
+ }
+ else if (count > 1)
+ {
+ previewBox.collapsed = true;
+ mediaSaveBox.collapsed = false;
+ mediaSaveButton.disabled = false;
+ splitter.collapsed = true;
+ tree.flex = 1;
+ }
+ else
+ {
+ previewBox.collapsed = false;
+ mediaSaveBox.collapsed = true;
+ mediaSaveButton.disabled = false;
+ splitter.collapsed = false;
+ tree.flex = 0;
+ makePreview(tree.view.selection.currentIndex);
+ }
+}
+
+// Makes the media preview (image, video, etc) for the selected row on
+// the media tab.
+function makePreview(row)
+{
+ var [url, type, sizeText, alt, count, item, isBG, imageSize, persistent, cachedType] = gImageView.data[row];
+ var isAudio = false;
+
+ setItemValue("imageurltext", url);
+ setItemValue("imagetext", item.imageText);
+ setItemValue("imagelongdesctext", item.longDesc);
+
+ // get cache info
+ var sourceText;
+ switch (persistent) {
+ case true:
+ sourceText = gBundle.getString("generalDiskCache");
+ break;
+ case false:
+ sourceText = gBundle.getString("generalMemoryCache");
+ break;
+ default:
+ sourceText = gBundle.getString("generalNotCached");
+ break;
+ }
+ setItemValue("imagesourcetext", sourceText);
+
+ // find out the file size
+ var sizeText;
+ if (imageSize && imageSize != -1) {
+ var kbSize = Math.round(imageSize / 1024 * 100) / 100;
+ sizeText = gBundle.getFormattedString("generalSize",
+ [formatNumber(kbSize),
+ formatNumber(imageSize)]);
+ }
+ else
+ sizeText = gBundle.getString("mediaUnknownNotCached");
+ setItemValue("imagesizetext", sizeText);
+
+ var mimeType = item.mimeType || cachedType;
+ var numFrames = item.numFrames;
+
+ var imageType;
+ if (mimeType) {
+ // We found the type, try to display it nicely
+ let imageMimeType = /^image\/(.*)/i.exec(mimeType);
+ if (imageMimeType) {
+ imageType = imageMimeType[1].toUpperCase();
+ if (numFrames > 1)
+ imageType = gBundle.getFormattedString("mediaAnimatedImageType",
+ [imageType, numFrames]);
+ else
+ imageType = gBundle.getFormattedString("mediaImageType", [imageType]);
+ }
+ else {
+ // the MIME type doesn't begin with image/, display the raw type
+ imageType = mimeType;
+ }
+ }
+ else {
+ // We couldn't find the type, fall back to the value in the treeview
+ imageType = type;
+ }
+
+ setItemValue("imagetypetext", imageType);
+
+ var imageContainer = document.getElementById("theimagecontainer");
+ var oldImage = document.getElementById("thepreviewimage");
+
+ var isProtocolAllowed = checkProtocol(gImageView.data[row]);
+ var isImageType = mimeType && mimeType.startsWith("image/");
+
+ var newImage = new Image;
+ newImage.id = "thepreviewimage";
+ var physWidth = 0, physHeight = 0;
+ var width = 0, height = 0;
+
+ if ((item.HTMLLinkElement || item.HTMLInputElement ||
+ item.HTMLImageElement || item.SVGImageElement ||
+ (item.HTMLObjectElement && isImageType) ||
+ (item.HTMLEmbedElement && isImageType) ||
+ isBG) && isProtocolAllowed) {
+ // We need to wait for the image to finish loading before
+ // using width & height.
+ newImage.addEventListener("loadend", function() {
+ physWidth = newImage.width || 0;
+ physHeight = newImage.height || 0;
+
+ // "width" and "height" attributes must be set to newImage,
+ // even if there is no "width" or "height attribute in item;
+ // otherwise, the preview image cannot be displayed correctly.
+ // Since the image might have been loaded out-of-process, we expect
+ // the item to tell us its width / height dimensions. Failing that
+ // the item should tell us the natural dimensions of the image. Finally
+ // failing that, we'll assume that the image was never loaded in the
+ // other process (this can be true for favicons, for example), and so
+ // we'll assume that we can use the natural dimensions of the newImage
+ // we just created. If the natural dimensions of newImage are not known
+ // then the image is probably broken.
+ if (!isBG) {
+ newImage.width = ("width" in item && item.width) ||
+ newImage.naturalWidth;
+ newImage.height = ("height" in item && item.height) ||
+ newImage.naturalHeight;
+ }
+ else {
+ // The width and height of an HTML tag should not be used for its
+ // background image (for example, "table" can have "width" or "height"
+ // attributes).
+ newImage.width = item.naturalWidth || newImage.naturalWidth;
+ newImage.height = item.naturalHeight || newImage.naturalHeight;
+ }
+
+ if (item.SVGImageElement) {
+ newImage.width = item.SVGImageElementWidth;
+ newImage.height = item.SVGImageElementHeight;
+ }
+
+ width = newImage.width;
+ height = newImage.height;
+
+ document.getElementById("theimagecontainer").collapsed = false
+ document.getElementById("brokenimagecontainer").collapsed = true;
+
+ let imageSize = "";
+ if (url) {
+ if (width != physWidth || height != physHeight) {
+ imageSize = gBundle.getFormattedString("mediaDimensionsScaled",
+ [formatNumber(physWidth),
+ formatNumber(physHeight),
+ formatNumber(width),
+ formatNumber(height)]);
+ } else {
+ imageSize = gBundle.getFormattedString("mediaDimensions",
+ [formatNumber(width),
+ formatNumber(height)]);
+ }
+ }
+ setItemValue("imagedimensiontext", imageSize);
+ }, {once: true});
+
+ newImage.setAttribute("src", url);
+ }
+ else {
+ // Handle the case where newImage is not used for width & height.
+ if (item.HTMLVideoElement && isProtocolAllowed) {
+ newImage = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
+ newImage.id = "thepreviewimage";
+ newImage.src = url;
+ newImage.controls = true;
+ width = physWidth = item.videoWidth;
+ height = physHeight = item.videoHeight;
+
+ document.getElementById("theimagecontainer").collapsed = false
+ document.getElementById("brokenimagecontainer").collapsed = true;
+ }
+ else if (item.HTMLAudioElement && isProtocolAllowed) {
+ newImage = new Audio;
+ newImage.id = "thepreviewimage";
+ newImage.src = url;
+ newImage.controls = true;
+ newImage.preload = "metadata";
+ isAudio = true;
+
+ document.getElementById("theimagecontainer").collapsed = false
+ document.getElementById("brokenimagecontainer").collapsed = true;
+ }
+ else {
+ // fallback image for protocols not allowed (e.g., javascript:)
+ // or elements not [yet] handled (e.g., object, embed).
+ document.getElementById("brokenimagecontainer").collapsed = false;
+ document.getElementById("theimagecontainer").collapsed = true;
+ }
+
+ let imageSize = "";
+ if (url && !isAudio) {
+ imageSize = gBundle.getFormattedString("mediaDimensions",
+ [formatNumber(width),
+ formatNumber(height)]);
+ }
+ setItemValue("imagedimensiontext", imageSize);
+ }
+
+ makeBlockImage(url);
+
+ oldImage.remove();
+ imageContainer.appendChild(newImage);
+}
+
+function makeBlockImage(url)
+{
+ var checkbox = document.getElementById("blockImage");
+ var imagePref = Services.prefs.getIntPref("permissions.default.image");
+ if (!(/^https?:/.test(url)) || imagePref == 2)
+ // We can't block the images from this host because either is is not
+ // for http(s) or we don't load images at all
+ checkbox.hidden = true;
+ else {
+ var uri = makeURI(url);
+ if (uri.host) {
+ checkbox.hidden = false;
+ checkbox.label = gBundle.getFormattedString("mediaBlockImage", [uri.host]);
+ var perm = Services.perms.testPermission(uri, "image");
+ checkbox.checked = perm == Services.perms.DENY_ACTION;
+ }
+ else
+ checkbox.hidden = true;
+ }
+}
+
+var imagePermissionObserver = {
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (document.getElementById("mediaPreviewBox").collapsed)
+ return;
+
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Ci.nsIPermission);
+ if (permission.type == "image") {
+ var imageTree = document.getElementById("imagetree");
+ var row = imageTree.currentIndex;
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var url = gImageView.data[row][COL_IMAGE_ADDRESS];
+ if (permission.matchesURI(makeURI(url), true))
+ makeBlockImage(url);
+ }
+ }
+ }
+}
+
+function getContentTypeFromHeaders(cacheEntryDescriptor)
+{
+ if (!cacheEntryDescriptor)
+ return null;
+
+ let headers = cacheEntryDescriptor.getMetaDataElement("response-head");
+ let type = /^Content-Type:\s*(.*?)\s*(?:\;|$)/mi.exec(headers);
+ return type && type[1];
+}
+
+function setItemValue(id, value, defaultString = gStrings.notSet)
+{
+ var item = document.getElementById(id);
+ if (value) {
+ item.disabled = false;
+ item.value = value;
+ }
+ else
+ {
+ item.value = defaultString;
+ item.disabled = true;
+ }
+}
+
+function formatNumber(number)
+{
+ return (+number).toLocaleString(); // coerce number to a numeric value before calling toLocaleString()
+}
+
+function formatDate(datestr, unknown)
+{
+ var date = new Date(datestr);
+ if (!date.valueOf())
+ return unknown;
+
+ const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
+ dateStyle: "full", timeStyle: "long"});
+ return dateTimeFormatter.format(date);
+}
+
+function getSelectedItems(linksMode)
+{
+ // linksMode is a boolean that is used to determine
+ // whether the getSelectedItems() function needs to
+ // run with urlSecurityCheck() or not.
+
+ var elem = document.commandDispatcher.focusedElement;
+
+ var view = elem.view;
+ var selection = view.selection;
+ var text = [], tmp = '';
+ var min = {}, max = {};
+
+ var count = selection.getRangeCount();
+
+ for (var i = 0; i < count; i++) {
+ selection.getRangeAt(i, min, max);
+
+ for (var row = min.value; row <= max.value; row++) {
+ tmp = view.getCellValue(row, null);
+ if (tmp)
+ {
+ try {
+ if (linksMode)
+ urlSecurityCheck(tmp, gDocInfo.principal);
+ text.push(tmp);
+ }
+ catch (e) {
+ }
+ }
+ }
+ }
+
+ return text;
+}
+
+function doCopy(isLinkMode)
+{
+ var text = getSelectedItems(isLinkMode);
+
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyString(text.join("\n"));
+}
+
+function doSelectAllMedia()
+{
+ var tree = document.getElementById("imagetree");
+
+ if (tree)
+ tree.view.selection.selectAll();
+}
+
+function doSelectAll()
+{
+ var elem = document.commandDispatcher.focusedElement;
+
+ if (elem && "treeBoxObject" in elem)
+ elem.view.selection.selectAll();
+}
+
+function selectImage() {
+ if (!gImageElement)
+ return;
+
+ var tree = document.getElementById("imagetree");
+ for (var i = 0; i < tree.view.rowCount; i++) {
+ // If the image row element is the image selected from
+ // the "View Image Info" context menu item.
+ let image = gImageView.data[i][COL_IMAGE_NODE];
+ if (!gImageView.data[i][COL_IMAGE_BG] &&
+ gImageElement.currentSrc == gImageView.data[i][COL_IMAGE_ADDRESS] &&
+ gImageElement.width == image.width &&
+ gImageElement.height == image.height &&
+ gImageElement.imageText == image.imageText) {
+ tree.view.selection.select(i);
+ tree.treeBoxObject.ensureRowIsVisible(i);
+ tree.focus();
+ return;
+ }
+ }
+}
+
+function checkProtocol(img)
+{
+ var url = img[COL_IMAGE_ADDRESS];
+ return /^data:image\//i.test(url) ||
+ /^(https?|ftp|file|about|chrome|resource):/.test(url);
+}
+
+function onOpenIn(mode)
+{
+ var linkList = getSelectedItems(true);
+
+ if (linkList.length)
+ openUILinkArrayIn(linkList, mode);
+}
diff --git a/comm/suite/browser/pageinfo/pageInfo.xul b/comm/suite/browser/pageinfo/pageInfo.xul
new file mode 100644
index 0000000000..8f4dc2ae96
--- /dev/null
+++ b/comm/suite/browser/pageinfo/pageInfo.xul
@@ -0,0 +1,531 @@
+<?xml version="1.0"?><!-- -*- Mode: nXML; indent-tabs-mode: nil -*- -->
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://navigator/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://navigator/content/pageinfo/pageInfo.css" type="text/css"?>
+<?xml-stylesheet href="chrome://navigator/skin/pageInfo.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % pageInfoDTD SYSTEM "chrome://navigator/locale/pageInfo.dtd">
+ %pageInfoDTD;
+]>
+
+<window id="main-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="Browser:page-info"
+ onload="onLoadPageInfo()"
+ onunload="onUnloadPageInfo()"
+ align="stretch" style="&pageInfoWindow.dimensions;"
+ persist="screenX screenY width height sizemode">
+
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://global/content/contentAreaUtils.js"/>
+ <script src="chrome://global/content/treeUtils.js"/>
+ <script src="chrome://communicator/content/utilityOverlay.js"/>
+ <script src="chrome://navigator/content/pageinfo/pageInfo.js"/>
+ <script src="chrome://navigator/content/pageinfo/feeds.js"/>
+ <script src="chrome://navigator/content/pageinfo/permissions.js"/>
+ <script src="chrome://navigator/content/pageinfo/security.js"/>
+ <script src="chrome://help/content/contextHelp.js"/>
+ <script src="chrome://communicator/content/tasksOverlay.js"/>
+
+ <stringbundleset id="pageinfobundleset">
+ <stringbundle id="pageinfobundle" src="chrome://navigator/locale/pageInfo.properties"/>
+ <stringbundle id="pkiBundle" src="chrome://pippki/locale/pippki.properties"/>
+ </stringbundleset>
+
+ <commandset id="pageInfoCommandSet">
+ <command id="cmd_close" oncommand="window.close();"/>
+ <command id="cmd_help" oncommand="doHelpButton();"/>
+ <command id="cmd_copy" oncommand="doCopy(false);"/>
+ <command id="cmd_selectall" oncommand="doSelectAll();"/>
+
+ <!-- links tab -->
+ <command id="cmd_openInNewTab" oncommand="onOpenIn('tab');"/>
+ <command id="cmd_openInNewWindow" oncommand="onOpenIn('window');"/>
+ <command id="cmd_copyLinks" oncommand="doCopy(true);"/>
+ </commandset>
+
+ <keyset>
+ <key key="&closeWindow.key;" modifiers="accel" command="cmd_close"/>
+ <key keycode="VK_ESCAPE" command="cmd_close"/>
+ <key key="." modifiers="meta" command="cmd_close"/>
+ <key keycode="VK_F1" command="cmd_help"/>
+ <key key="&openHelpMac.key;" modifiers="meta" command="cmd_help"/>
+ <key key="&copy.key;" modifiers="accel" command="cmd_copy"/>
+ <key key="&selectall.key;" modifiers="accel" command="cmd_selectall"/>
+ <key key="&selectall.key;" modifiers="alt" command="cmd_selectall"/>
+ </keyset>
+
+ <menupopup id="picontext">
+ <menuitem id="menu_selectall" label="&selectall.label;" command="cmd_selectall" accesskey="&selectall.accesskey;"/>
+ <menuitem id="menu_copy" label="&copy.label;" command="cmd_copy" accesskey="&copy.accesskey;"/>
+ </menupopup>
+
+ <menupopup id="piLinksContext">
+ <menuitem id="menu_openInNewTab"
+ label="&openInNewTab.label;"
+ command="cmd_openInNewTab"
+ accesskey="&openInNewTab.accesskey;"/>
+ <menuitem id="menu_openInNewWindow"
+ label="&openInNewWindow.label;"
+ command="cmd_openInNewWindow"
+ accesskey="&openInNewWindow.accesskey;"/>
+ <menuitem id="menu_selectall_links"
+ label="&selectall.label;"
+ command="cmd_selectall"
+ accesskey="&selectall.accesskey;"/>
+ <menuitem id="menu_copyLinks"
+ label="&copyLinks.label;"
+ command="cmd_copyLinks"
+ accesskey="&copyLinks.accesskey;"/>
+ </menupopup>
+
+ <tabbox id="tabbox" flex="1">
+ <vbox id="dragbox">
+ <tabs id="tabs"
+ onselect="[gImageView, gFormView, gLinkView].forEach(ensureSelection);">
+ <tab id="generalTab"
+ label="&generalTab;"
+ accesskey="&generalTab.accesskey;"/>
+ <tab id="mediaTab"
+ label="&mediaTab;"
+ accesskey="&mediaTab.accesskey;"/>
+ <tab id="feedTab"
+ label="&feedTab;"
+ accesskey="&feedTab.accesskey;"/>
+ <tab id="permTab"
+ label="&permTab;"
+ accesskey="&permTab.accesskey;"/>
+ <tab id="formsTab"
+ label="&formsTab;"
+ accesskey="&formsTab.accesskey;"/>
+ <tab id="linksTab"
+ label="&linksTab;"
+ accesskey="&linksTab.accesskey;"/>
+ <tab id="securityTab"
+ label="&securityTab;"
+ accesskey="&securityTab.accesskey;"/>
+ <!-- Others added by overlay -->
+ </tabs>
+ </vbox>
+
+ <tabpanels id="mainDeck" flex="1">
+ <!-- General page information -->
+ <vbox id="generalPanel">
+ <grid>
+ <columns>
+ <column/>
+ <column class="gridSeparator"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row id="generalTitle">
+ <label control="titletext" value="&generalTitle;"/>
+ <separator/>
+ <textbox readonly="true" id="titletext"/>
+ </row>
+ <row>
+ <label control="urltext" value="&generalURL;"/>
+ <separator/>
+ <textbox readonly="true" id="urltext" class="urltext"/>
+ </row>
+ <row>
+ <separator class="thin"/>
+ </row>
+ <row>
+ <label control="typetext" value="&generalType;"/>
+ <separator/>
+ <textbox readonly="true" id="typetext"/>
+ </row>
+ <row>
+ <label control="modetext" value="&generalMode;"/>
+ <separator/>
+ <textbox readonly="true" crop="end" id="modetext"/>
+ </row>
+ <row>
+ <label control="encodingtext" value="&generalEncoding2;"/>
+ <separator/>
+ <textbox readonly="true" id="encodingtext"/>
+ </row>
+ <row>
+ <label control="sizetext" value="&generalSize;"/>
+ <separator/>
+ <textbox readonly="true" id="sizetext"/>
+ </row>
+ <row>
+ <label control="refertext" value="&generalReferrer;"/>
+ <separator/>
+ <textbox readonly="true" id="refertext" class="urltext"/>
+ </row>
+ <row>
+ <separator class="thin"/>
+ </row>
+ <row>
+ <label control="modifiedtext" value="&generalModified;"/>
+ <separator/>
+ <textbox readonly="true" id="modifiedtext"/>
+ </row>
+ </rows>
+ </grid>
+ <separator class="thin"/>
+ <groupbox id="metaTags" flex="1">
+ <caption id="metaTagsCaption"/>
+ <tree id="metatree" flex="1" hidecolumnpicker="true" contextmenu="picontext">
+ <treecols>
+ <treecol id="meta-name" label="&generalMetaName;"
+ persist="width" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="meta-content" label="&generalMetaContent;"
+ persist="width" flex="4"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+ </groupbox>
+ <groupbox id="securityBox">
+ <caption id="securityBoxCaption" label="&securityHeader;"/>
+ <description id="general-security-identity" class="indent header"/>
+ <description id="general-security-privacy" class="indent header"/>
+ <hbox align="right">
+ <button id="security-view-details" label="&generalSecurityDetails;"
+ accesskey="&generalSecurityDetails.accesskey;"
+ oncommand="onClickMore();"/>
+ </hbox>
+ </groupbox>
+ </vbox>
+
+ <!-- Media information -->
+ <vbox id="mediaPanel">
+ <tree id="imagetree" onselect="onImageSelect();" contextmenu="picontext"
+ ondragstart="onBeginLinkDrag(event,'image-address','image-alt')">
+ <treecols>
+ <treecol sortSeparators="true" primary="true" persist="width" flex="10"
+ width="10" id="image-address" label="&mediaAddress;"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="2"
+ width="2" id="image-type" label="&mediaType;"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="2"
+ width="2" id="image-size" label="&mediaSize;"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="4"
+ width="4" id="image-alt" label="&mediaAltHeader;"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="1"
+ width="1" id="image-count" label="&mediaCount;"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+ <splitter orient="vertical" id="mediaSplitter"/>
+ <vbox flex="1" id="mediaPreviewBox">
+ <grid id="mediaGrid">
+ <columns>
+ <column id="mediaLabelColumn"/>
+ <column class="gridSeparator"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row>
+ <label control="imageurltext" value="&mediaLocation;"/>
+ <separator/>
+ <textbox readonly="true" id="imageurltext" class="urltext"/>
+ </row>
+ <row>
+ <label control="imagetypetext" value="&generalType;"/>
+ <separator/>
+ <textbox readonly="true" id="imagetypetext"/>
+ </row>
+ <row>
+ <label control="imagesourcetext" value="&generalSource;"/>
+ <separator/>
+ <textbox readonly="true" id="imagesourcetext"/>
+ </row>
+ <row>
+ <label control="imagesizetext" value="&generalSize;"/>
+ <separator/>
+ <textbox readonly="true" id="imagesizetext"/>
+ </row>
+ <row>
+ <label control="imagedimensiontext" value="&mediaDimension;"/>
+ <separator/>
+ <textbox readonly="true" id="imagedimensiontext"/>
+ </row>
+ <row>
+ <label control="imagetext" value="&mediaText;"/>
+ <separator/>
+ <textbox readonly="true" id="imagetext"/>
+ </row>
+ <row>
+ <label control="imagelongdesctext" value="&mediaLongdesc;"/>
+ <separator/>
+ <textbox readonly="true" id="imagelongdesctext"/>
+ </row>
+ </rows>
+ </grid>
+ <hbox align="end">
+ <vbox>
+ <checkbox id="blockImage"
+ hidden="true"
+ oncommand="onBlockImage(this.checked);"
+ accesskey="&mediaBlockImage.accesskey;"/>
+ <label control="thepreviewimage" value="&mediaPreview;" class="header"/>
+ </vbox>
+ <spacer flex="1"/>
+ <button label="&selectall.label;" accesskey="&selectall.accesskey;"
+ id="selectallbutton"
+ oncommand="doSelectAllMedia();"/>
+ <button label="&mediaSaveAs;" accesskey="&mediaSaveAs.accesskey;"
+ icon="save" id="imagesaveasbutton" disabled="true"
+ oncommand="saveMedia();"/>
+ </hbox>
+ <vbox class="inset iframe" flex="1" pack="center">
+ <hbox id="theimagecontainer" pack="center">
+ <image id="thepreviewimage"/>
+ </hbox>
+ <hbox id="brokenimagecontainer" pack="center" collapsed="true">
+ <image id="brokenimage" src="resource://gre-resources/broken-image.png"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ <hbox id="mediaSaveBox" collapsed="true">
+ <spacer flex="1"/>
+ <button label="&mediaSaveAs;" accesskey="&mediaSaveAs2.accesskey;"
+ icon="save" oncommand="saveMedia();"/>
+ </hbox>
+ </vbox>
+
+ <!-- Feeds -->
+ <vbox id="feedPanel">
+ <richlistbox id="feedListbox" flex="1"/>
+ </vbox>
+
+ <!-- Permissions -->
+ <vbox id="permPanel">
+ <hbox>
+ <label control="hostText" value="&permissionsFor;"/>
+ <textbox id="hostText" class="header" readonly="true"
+ crop="end" flex="1"/>
+ </hbox>
+
+ <!--
+ The richlist below is generated by permissions.js.
+ The labels point to the radio groups to give the radio buttons
+ an accessible context. The accessible context for the preceeding
+ checkbox is already taken care of through the richlistitem grouping.
+ -->
+ <richlistbox id="permList" flex="1"/>
+ </vbox>
+
+ <!-- Form information -->
+ <vbox>
+ <tree id="formtree" class="fixedsize" onselect="onFormSelect();" contextmenu="picontext">
+ <treecols>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" primary="true" persist="width" flex="1"
+ width="1" id="form-name" label="&formName;"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="3"
+ width="3" id="form-method" label="&formMethod;"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="2"
+ width="2" id="form-action" label="&formAction;"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+ <splitter orient="vertical"/>
+ <vbox flex="1">
+ <textbox readonly="true" class="header" id="formname"/>
+ <grid>
+ <columns>
+ <column/>
+ <column style="width: .5em;"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row>
+ <label control="formenctype" value="&formEncoding;"/>
+ <separator/>
+ <textbox readonly="true" id="formenctype"/>
+ </row>
+ <row>
+ <label control="formtarget" value="&formTarget;"/>
+ <separator/>
+ <textbox readonly="true" class="label" id="formtarget"/>
+ </row>
+ </rows>
+ </grid>
+ <label control="formpreview" class="header" value="&formFields;"/>
+ <tree id="formpreview" flex="1" contextmenu="picontext">
+ <treecols>
+ <treecol sortSeparators="true" primary="true" persist="width" flex="3"
+ width="3" id="field-label" label="&formLabel;"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="3"
+ width="3" id="field-field" label="&formFName;"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="1"
+ width="1" id="field-type" label="&formType;"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="3"
+ width="3" id="field-value" label="&formCValue;"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+ </vbox>
+ </vbox>
+
+ <!-- Link info -->
+ <vbox>
+ <tree id="linktree"
+ flex="1"
+ ondragstart="onBeginLinkDrag(event,'link-address','link-name')"
+ contextmenu="piLinksContext">
+ <treecols>
+ <treecol sortSeparators="true" primary="true" persist="width" flex="5"
+ width="5" id="link-name" label="&linkName;"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="7"
+ width="7" id="link-address" label="&linkAddress;"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="2"
+ width="2" id="link-type" label="&linkType;"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="2"
+ width="2" id="link-target" label="&linkTarget;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="1"
+ width="1" id="link-accesskey" label="&linkAccessKey;" hidden="true"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+ </vbox>
+
+ <!-- Security & Privacy -->
+ <vbox id="securityPanel">
+ <!-- Identity Section -->
+ <groupbox id="security-identity-groupbox" flex="1">
+ <caption id="security-identity" label="&securityView.identity.header;"/>
+ <hbox>
+ <image id="identity-icon"/>
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row><!-- Domain -->
+ <label control="security-identity-domain-value"
+ id="security-identity-domain-label"
+ class="fieldLabel"
+ value="&securityView.identity.domain;"/>
+ <textbox id="security-identity-domain-value"
+ class="fieldValue" readonly="true"/>
+ </row>
+ <row><!-- Owner -->
+ <label control="security-identity-owner-value"
+ id="security-identity-owner-label"
+ class="fieldLabel"
+ value="&securityView.identity.owner;"/>
+ <textbox id="security-identity-owner-value"
+ class="fieldValue" readonly="true"/>
+ </row>
+ <row><!-- Verifier -->
+ <label control="security-identity-verifier-value"
+ id="security-identity-verifier-label"
+ class="fieldLabel"
+ value="&securityView.identity.verifier;"/>
+ <textbox id="security-identity-verifier-value"
+ class="fieldValue" readonly="true"/>
+ </row>
+ <!-- Certificate Validity -->
+ <row id="security-identity-validity-row">
+ <label id="security-identity-validity-label"
+ class="fieldLabel"
+ value="&securityView.identity.validity;"
+ control="security-identity-validity-value"/>
+ <textbox id="security-identity-validity-value"
+ class="fieldValue" readonly="true" />
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+ <spacer flex="1"/>
+ <hbox pack="end"><!-- Cert button -->
+ <button id="security-view-cert" label="&securityView.certView;"
+ accesskey="&securityView.accesskey;"
+ oncommand="security.viewCert();"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Privacy & History section -->
+ <groupbox id="security-privacy-groupbox" flex="1">
+ <caption id="security-privacy" label="&securityView.privacy.header;" />
+ <grid>
+ <columns>
+ <column flex="1"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center"><!-- History -->
+ <label id="security-privacy-history-label"
+ control="security-privacy-history-value"
+ class="fieldLabel">&securityView.privacy.history;</label>
+ <textbox id="security-privacy-history-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ readonly="true"/>
+ </row>
+ <row align="center"><!-- Cookies -->
+ <label id="security-privacy-cookies-label"
+ control="security-privacy-cookies-value"
+ class="fieldLabel">&securityView.privacy.cookies;</label>
+ <hbox align="center">
+ <textbox id="security-privacy-cookies-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ flex="1"
+ readonly="true"/>
+ <button id="security-view-cookies"
+ label="&securityView.privacy.viewCookies;"
+ accesskey="&securityView.privacy.viewCookies.accessKey;"
+ oncommand="security.viewCookies();"/>
+ </hbox>
+ </row>
+ <row align="center"><!-- Passwords -->
+ <label id="security-privacy-passwords-label"
+ control="security-privacy-passwords-value"
+ class="fieldLabel">&securityView.privacy.passwords;</label>
+ <hbox align="center">
+ <textbox id="security-privacy-passwords-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ flex="1"
+ readonly="true"/>
+ <button id="security-view-password"
+ label="&securityView.privacy.viewPasswords;"
+ accesskey="&securityView.privacy.viewPasswords.accessKey;"
+ oncommand="security.viewPasswords();"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- Technical Details section -->
+ <groupbox id="security-technical-groupbox" flex="1">
+ <caption id="security-technical" label="&securityView.technical.header;" />
+ <vbox flex="1">
+ <label id="security-technical-shortform" class="fieldValue"/>
+ <description id="security-technical-longform1" class="fieldLabel"/>
+ <description id="security-technical-longform2" class="fieldLabel"/>
+ <description id="security-technical-certificate-transparency" class="fieldLabel"/>
+ </vbox>
+ </groupbox>
+ </vbox>
+
+ <!-- Others added by overlay -->
+ </tabpanels>
+ </tabbox>
+</window>
diff --git a/comm/suite/browser/pageinfo/permissions.js b/comm/suite/browser/pageinfo/permissions.js
new file mode 100644
index 0000000000..7d126d6ea7
--- /dev/null
+++ b/comm/suite/browser/pageinfo/permissions.js
@@ -0,0 +1,204 @@
+/* -*- 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/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0. */
+
+const { SitePermissions } = ChromeUtils.import("resource:///modules/SitePermissions.jsm");
+const { BrowserUtils } = ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm"");
+
+var gPermPrincipal;
+
+// Array of permissionIDs sorted alphabetically by label.
+var gPermissions = SitePermissions.listPermissions().sort((a, b) => {
+ let firstLabel = SitePermissions.getPermissionLabel(a);
+ let secondLabel = SitePermissions.getPermissionLabel(b);
+ return firstLabel.localeCompare(secondLabel);
+});
+
+var permissionObserver = {
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Ci.nsIPermission);
+ if (permission.matches(gPermPrincipal, true) &&
+ gPermissions.includes(permission.type)) {
+ initRow(permission.type);
+ }
+ }
+ }
+};
+
+function initPermission()
+{
+ onUnloadRegistry.push(onUnloadPermission);
+ onResetRegistry.push(onUnloadPermission);
+}
+
+function onLoadPermission(uri, principal)
+{
+ var permTab = document.getElementById("permTab");
+ if (!SitePermissions.isSupportedPrincipal(principal)) {
+ permTab.hidden = true;
+ return;
+ }
+
+ gPermPrincipal = principal;
+ if (gPermPrincipal && !gPermPrincipal.isSystemPrincipal) {
+ var hostText = document.getElementById("hostText");
+ hostText.value = gPermPrincipal.origin;
+ Services.obs.addObserver(permissionObserver, "perm-changed");
+ }
+ for (var i of gPermissions)
+ initRow(i);
+ permTab.hidden = false;
+}
+
+function onUnloadPermission()
+{
+ if (gPermPrincipal && !gPermPrincipal.isSystemPrincipal) {
+ Services.obs.removeObserver(permissionObserver, "perm-changed");
+ }
+}
+
+function initRow(aPartId)
+{
+ createRow(aPartId);
+
+ var checkbox = document.getElementById(aPartId + "Def");
+ var command = document.getElementById("cmd_" + aPartId + "Toggle");
+ if (gPermPrincipal && gPermPrincipal.isSystemPrincipal) {
+ checkbox.checked = false;
+ checkbox.setAttribute("disabled", "true");
+ command.setAttribute("disabled", "true");
+ document.getElementById(aPartId + "RadioGroup").selectedItem = null;
+ return;
+ }
+ checkbox.removeAttribute("disabled");
+ var {state} = SitePermissions.getForPrincipal(gPermPrincipal, aPartId);
+ let defaultState = SitePermissions.getDefault(aPartId);
+
+ // Since cookies preferences have many different possible configuration states
+ // we don't consider any permission except "no permission" to be default.
+ if (aPartId == "cookie") {
+ state = Services.perms.testPermissionFromPrincipal(gPermPrincipal, "cookie");
+
+ if (state == SitePermissions.UNKNOWN) {
+ checkbox.checked = true;
+ command.setAttribute("disabled", "true");
+ // Don't select any item in the radio group, as we can't
+ // confidently say that all cookies on the site will be allowed.
+ let radioGroup = document.getElementById("cookieRadioGroup");
+ radioGroup.selectedItem = null;
+ } else {
+ checkbox.checked = false;
+ command.removeAttribute("disabled");
+ }
+
+ setRadioState(aPartId, state);
+ return;
+ }
+
+ if (state != defaultState) {
+ checkbox.checked = false;
+ command.removeAttribute("disabled");
+ }
+ else {
+ checkbox.checked = true;
+ command.setAttribute("disabled", "true");
+ }
+ setRadioState(aPartId, state);
+}
+
+function createRow(aPartId) {
+ let rowId = "perm-" + aPartId + "-row";
+ if (document.getElementById(rowId))
+ return;
+
+ let commandId = "cmd_" + aPartId + "Toggle";
+ let labelId = "perm-" + aPartId + "-label";
+ let radiogroupId = aPartId + "RadioGroup";
+
+ let command = document.createElement("command");
+ command.setAttribute("id", commandId);
+ command.setAttribute("oncommand", "onRadioClick('" + aPartId + "');");
+ document.getElementById("pageInfoCommandSet").appendChild(command);
+
+ let row = document.createElement("richlistitem");
+ row.setAttribute("id", rowId);
+ row.setAttribute("class", "permission");
+ row.setAttribute("orient", "vertical");
+
+ let label = document.createElement("label");
+ label.setAttribute("id", labelId);
+ label.setAttribute("control", radiogroupId);
+ label.setAttribute("value", SitePermissions.getPermissionLabel(aPartId));
+ label.setAttribute("class", "permissionLabel");
+ row.appendChild(label);
+
+ let controls = document.createElement("hbox");
+ controls.setAttribute("role", "group");
+ controls.setAttribute("aria-labelledby", labelId);
+
+ let checkbox = document.createElement("checkbox");
+ checkbox.setAttribute("id", aPartId + "Def");
+ checkbox.setAttribute("oncommand", "onCheckboxClick('" + aPartId + "');");
+ checkbox.setAttribute("label", gBundle.getString("permissions.useDefault"));
+ controls.appendChild(checkbox);
+
+ let spacer = document.createElement("spacer");
+ spacer.setAttribute("flex", "1");
+ controls.appendChild(spacer);
+
+ let radiogroup = document.createElement("radiogroup");
+ radiogroup.setAttribute("id", radiogroupId);
+ radiogroup.setAttribute("orient", "horizontal");
+ for (let state of SitePermissions.getAvailableStates(aPartId)) {
+ let radio = document.createElement("radio");
+ radio.setAttribute("id", aPartId + "#" + state);
+ radio.setAttribute("label", SitePermissions.getMultichoiceStateLabel(aPartId, state));
+ radio.setAttribute("command", commandId);
+ radiogroup.appendChild(radio);
+ }
+ controls.appendChild(radiogroup);
+
+ row.appendChild(controls);
+
+ document.getElementById("permList").appendChild(row);
+}
+
+function onCheckboxClick(aPartId)
+{
+ var command = document.getElementById("cmd_" + aPartId + "Toggle");
+ var checkbox = document.getElementById(aPartId + "Def");
+ if (checkbox.checked) {
+ SitePermissions.removeFromPrincipal(gPermPrincipal, aPartId);
+ command.setAttribute("disabled", "true");
+ }
+ else {
+ onRadioClick(aPartId);
+ command.removeAttribute("disabled");
+ }
+}
+
+function onRadioClick(aPartId)
+{
+ var radioGroup = document.getElementById(aPartId + "RadioGroup");
+ let permission;
+ if (radioGroup.selectedItem) {
+ permission = parseInt(radioGroup.selectedItem.id.split("#")[1]);
+ } else {
+ permission = SitePermissions.getDefault(aPartId);
+ }
+ SitePermissions.setForPrincipal(gPermPrincipal, aPartId, permission);
+}
+
+function setRadioState(aPartId, aValue)
+{
+ var radio = document.getElementById(aPartId + "#" + aValue);
+ if (radio) {
+ radio.radioGroup.selectedItem = radio;
+ }
+}
diff --git a/comm/suite/browser/pageinfo/security.js b/comm/suite/browser/pageinfo/security.js
new file mode 100644
index 0000000000..f6357c9ac4
--- /dev/null
+++ b/comm/suite/browser/pageinfo/security.js
@@ -0,0 +1,354 @@
+/* -*- 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/. */
+
+ChromeUtils.defineModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+var security = {
+ init: function(uri, windowInfo) {
+ this.uri = uri;
+ this.windowInfo = windowInfo;
+ },
+
+ // Display the server certificate (static)
+ viewCert : function() {
+ var cert = security._cert;
+ viewCertHelper(window, cert);
+ },
+
+ _getSecurityInfo : function() {
+ // We don't have separate info for a frame, return null until further notice
+ // (see bug 138479)
+ if (!this.windowInfo.isTopWindow)
+ return null;
+
+ var hostName = this.windowInfo.hostName;
+
+ var ui = security._getSecurityUI();
+ if (!ui)
+ return null;
+
+ var isBroken =
+ (ui.state & Ci.nsIWebProgressListener.STATE_IS_BROKEN);
+ var isMixed =
+ (ui.state & (Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT |
+ Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT));
+ var isInsecure =
+ (ui.state & Ci.nsIWebProgressListener.STATE_IS_INSECURE);
+ var isEV =
+ (ui.state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL);
+ var status = ui.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
+
+ if (!isInsecure && status) {
+ status.QueryInterface(Ci.nsISSLStatus);
+ var cert = status.serverCert;
+ var issuerName = cert.issuerOrganization || cert.issuerName;
+
+ var retval = {
+ hostName : hostName,
+ cAName : issuerName,
+ encryptionAlgorithm : undefined,
+ encryptionStrength : undefined,
+ version: undefined,
+ isBroken : isBroken,
+ isMixed : isMixed,
+ isEV : isEV,
+ cert : cert,
+ certificateTransparency : undefined,
+ };
+
+ var version;
+ try {
+ retval.encryptionAlgorithm = status.cipherName;
+ retval.encryptionStrength = status.secretKeyLength;
+ version = status.protocolVersion;
+ }
+ catch (e) {
+ }
+
+ switch (version) {
+ case Ci.nsISSLStatus.SSL_VERSION_3:
+ retval.version = "SSL 3";
+ break;
+ case Ci.nsISSLStatus.TLS_VERSION_1:
+ retval.version = "TLS 1.0";
+ break;
+ case Ci.nsISSLStatus.TLS_VERSION_1_1:
+ retval.version = "TLS 1.1";
+ break;
+ case Ci.nsISSLStatus.TLS_VERSION_1_2:
+ retval.version = "TLS 1.2";
+ break;
+ case Ci.nsISSLStatus.TLS_VERSION_1_3:
+ retval.version = "TLS 1.3";
+ break;
+ }
+
+ // Select the status text to display for Certificate Transparency.
+ // Since we do not yet enforce the CT Policy on secure connections,
+ // we must not complain on policy discompliance (it might be viewed
+ // as a security issue by the user).
+ switch (status.certificateTransparencyStatus) {
+ case Ci.nsISSLStatus.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE:
+ case Ci.nsISSLStatus.CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS:
+ case Ci.nsISSLStatus.CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS:
+ retval.certificateTransparency = null;
+ break;
+ case Ci.nsISSLStatus.CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT:
+ retval.certificateTransparency = "Compliant";
+ break;
+ }
+
+ return retval;
+ }
+ return {
+ hostName : hostName,
+ cAName : "",
+ encryptionAlgorithm : "",
+ encryptionStrength : 0,
+ version: "",
+ isBroken : isBroken,
+ isMixed : isMixed,
+ isEV : isEV,
+ cert : null,
+ certificateTransparency : null,
+ };
+ },
+
+ // Find the secureBrowserUI object (if present)
+ _getSecurityUI : function() {
+ if (window.opener.gBrowser)
+ return window.opener.gBrowser.securityUI;
+ return null;
+ },
+
+ /**
+ * Open the cookie manager window
+ */
+ viewCookies : function()
+ {
+ var eTLD;
+ try {
+ eTLD = Services.eTLD.getBaseDomain(this.uri);
+ } catch (e) {
+ // getBaseDomain will fail if the host is an IP address or is empty
+ eTLD = this.uri.asciiHost;
+ }
+
+ toDataManager(eTLD + '|cookies');
+ },
+
+ /**
+ * Open the login manager window
+ */
+ viewPasswords : function()
+ {
+ toDataManager(this._getSecurityInfo().hostName + '|passwords');
+ },
+
+ _cert : null
+};
+
+function securityOnLoad(uri, windowInfo) {
+ security.init(uri, windowInfo);
+
+ var info = security._getSecurityInfo();
+ if (!info || uri.scheme === "about") {
+ document.getElementById("securityTab").hidden = true;
+ document.getElementById("securityBox").hidden = true;
+ return;
+ }
+
+ document.getElementById("securityTab").hidden = false;
+ document.getElementById("securityBox").hidden = false;
+
+ const pageInfoBundle = document.getElementById("pageinfobundle");
+
+ /* Set Identity section text */
+ setText("security-identity-domain-value", info.hostName);
+
+ var owner;
+ var verifier;
+ var generalPageIdentityString;
+ var identityClass;
+ var validity;
+ if (info.cert && !info.isBroken) {
+ validity = info.cert.validity.notAfterLocalDay;
+
+ // Try to pull out meaningful values. Technically these fields are optional
+ // so we'll employ fallbacks where appropriate. The EV spec states that Org
+ // fields must be specified for subject and issuer so that case is simpler.
+ if (info.isEV) {
+ owner = info.cert.organization;
+ verifier = info.cAName;
+ generalPageIdentityString =
+ pageInfoBundle.getFormattedString("generalSiteIdentity",
+ [owner, verifier]);
+ identityClass = "verifiedIdentity";
+ } else {
+ // Technically, a non-EV cert might specify an owner in the O field or
+ // not, depending on the CA's issuing policies. However we don't have any
+ // programmatic way to tell those apart, and no policy way to establish
+ // which organization vetting standards are good enough (that's what EV is
+ // for) so we default to treating these certs as domain-validated only.
+ owner = pageInfoBundle.getString("securityNoOwner");
+ verifier = info.cAName || info.cert.issuerCommonName || info.cert.issuerName;
+ generalPageIdentityString = owner;
+ identityClass = "verifiedDomain";
+ }
+ } else {
+ // We don't have valid identity credentials.
+ owner = pageInfoBundle.getString("securityNoOwner");
+ verifier = pageInfoBundle.getString("notSet");
+ generalPageIdentityString = owner;
+ identityClass = "";
+ }
+
+ setText("security-identity-owner-value", owner);
+ setText("security-identity-verifier-value", verifier);
+ setText("general-security-identity", generalPageIdentityString);
+ document.getElementById("identity-icon").className = identityClass;
+ if (validity) {
+ setText("security-identity-validity-value", validity);
+ } else {
+ document.getElementById("security-identity-validity-row").hidden = true;
+ }
+
+ /* Manage the View Cert button*/
+ if (info.cert)
+ security._cert = info.cert;
+ document.getElementById("security-view-cert").collapsed = !info.cert;
+
+ /* Set Privacy & History section text */
+ var yesStr = pageInfoBundle.getString("yes");
+ var noStr = pageInfoBundle.getString("no");
+
+ var hasCookies = hostHasCookies(uri);
+ setText("security-privacy-cookies-value", hasCookies ? yesStr : noStr);
+ document.getElementById("security-view-cookies").disabled = !hasCookies;
+ var hasPasswords = realmHasPasswords(uri);
+ setText("security-privacy-passwords-value", hasPasswords ? yesStr : noStr);
+ document.getElementById("security-view-password").disabled = !hasPasswords;
+
+ var visitCount = previousVisitCount(info.hostName);
+ let visitCountStr = visitCount > 0
+ ? PluralForm.get(visitCount, pageInfoBundle.getString("securityVisitsNumber"))
+ .replace("#1", visitCount.toLocaleString())
+ : pageInfoBundle.getString("securityNoVisits");
+ setText("security-privacy-history-value", visitCountStr);
+
+ /* Set the Technical Detail section messages */
+ const pkiBundle = document.getElementById("pkiBundle");
+ var hdr;
+ var msg1;
+ var msg2;
+
+ if (info.isBroken) {
+ if (info.isMixed) {
+ hdr = pkiBundle.getString("pageInfo_MixedContent");
+ msg1 = pkiBundle.getString("pageInfo_MixedContent2");
+ } else {
+ hdr = pkiBundle.getFormattedString("pageInfo_BrokenEncryption",
+ [info.encryptionAlgorithm,
+ info.encryptionStrength + "",
+ info.version]);
+ msg1 = pkiBundle.getString("pageInfo_WeakCipher");
+ }
+ msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
+ } else if (info.encryptionStrength > 0) {
+ hdr = pkiBundle.getFormattedString("pageInfo_EncryptionWithBitsAndProtocol",
+ [info.encryptionAlgorithm,
+ info.encryptionStrength + "",
+ info.version]);
+ msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2");
+ security._cert = info.cert;
+ } else {
+ hdr = pkiBundle.getString("pageInfo_NoEncryption");
+ if (info.hostName != null) {
+ msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [info.hostName]);
+ } else {
+ msg1 = pkiBundle.getString("pageInfo_Privacy_None4");
+ }
+ msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
+ }
+ setText("security-technical-shortform", hdr);
+ setText("security-technical-longform1", msg1);
+ setText("security-technical-longform2", msg2);
+ setText("general-security-privacy", hdr);
+
+ const ctStatus =
+ document.getElementById("security-technical-certificate-transparency");
+ if (info.certificateTransparency) {
+ ctStatus.hidden = false;
+ ctStatus.value = pkiBundle.getString(
+ "pageInfo_CertificateTransparency_" + info.certificateTransparency);
+ } else {
+ ctStatus.hidden = true;
+ }
+}
+
+function setText(id, value)
+{
+ var element = document.getElementById(id);
+ if (!element)
+ return;
+ if (element.localName == "textbox" || element.localName == "label")
+ element.value = value;
+ else
+ element.textContent = value;
+}
+
+function viewCertHelper(parent, cert)
+{
+ if (!cert)
+ return;
+
+ var cd = Cc[CERTIFICATEDIALOGS_CONTRACTID].getService(nsICertificateDialogs);
+ cd.viewCert(parent, cert);
+}
+
+/**
+ * Return true iff we have cookies for uri.
+ */
+function hostHasCookies(aUri) {
+ return Services.cookies.countCookiesFromHost(aUri.asciiHost) > 0;
+}
+
+/**
+ * Return true iff realm (proto://host:port) (extracted from uri) has
+ * saved passwords
+ */
+function realmHasPasswords(aUri) {
+ return Services.logins.countLogins(aUri.prePath, "", "") > 0;
+}
+
+/**
+ * Return the number of previous visits recorded for host before today.
+ *
+ * @param host - the domain name to look for in history
+ */
+function previousVisitCount(host, endTimeReference) {
+ if (!host)
+ return false;
+
+ var historyService = Cc["@mozilla.org/browser/nav-history-service;1"]
+ .getService(Ci.nsINavHistoryService);
+
+ var options = historyService.getNewQueryOptions();
+ options.resultType = options.RESULTS_AS_VISIT;
+
+ // Search for visits to this host before today
+ var query = historyService.getNewQuery();
+ query.endTimeReference = query.TIME_RELATIVE_TODAY;
+ query.endTime = 0;
+ query.domain = host;
+
+ var result = historyService.executeQuery(query, options);
+ result.root.containerOpen = true;
+ var cc = result.root.childCount;
+ result.root.containerOpen = false;
+ return cc;
+}
diff --git a/comm/suite/browser/safeBrowsingOverlay.js b/comm/suite/browser/safeBrowsingOverlay.js
new file mode 100644
index 0000000000..3e92234472
--- /dev/null
+++ b/comm/suite/browser/safeBrowsingOverlay.js
@@ -0,0 +1,75 @@
+/* -*- 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 gSafeBrowsing = {
+ initMenuItems: function initMenuItems() {
+ // A blocked page will have a specific about:blocked content documentURI.
+ var docURI = content.document.documentURI;
+
+ // "reason" isn't currently used but it's here to make porting
+ // from Firefox easier and may also be useful in the future
+ // for further testing and setting menu items.
+ let reason;
+
+ // Show/hide the appropriate menu item.
+ // Initially allow report url and disallow reporting phishing error.
+ document.getElementById("reportPhishing").hidden = false;
+ document.getElementById("reportPhishingError").hidden = true;
+
+ if (docURI.startsWith("about:blocked")) {
+ // It's blocked so don't allow reporting again.
+ document.getElementById("reportPhishing").hidden = true;
+ // Test for blocked page.
+ if (/e=malwareBlocked/.test(docURI)) {
+ reason = "malware";
+ } else if (/e=unwantedBlocked/.test(docURI)) {
+ reason = "unwanted";
+ } else if (/e=deceptiveBlocked/.test(docURI)) {
+ reason = "phishing";
+ document.getElementById("reportPhishingError").hidden = false;
+ }
+ }
+
+ var broadcaster = document.getElementById("safeBrowsingBroadcaster");
+ var uri = getBrowser().currentURI;
+ if (uri && (uri.schemeIs("http") || uri.schemeIs("https")))
+ broadcaster.removeAttribute("disabled");
+ else
+ broadcaster.setAttribute("disabled", true);
+ },
+
+ /**
+ * Used to report a phishing page or a false positive
+ *
+ * @param name
+ * String One of "PhishMistake", "MalwareMistake", or "Phish"
+ * @param info
+ * Information about the reasons for blocking the resource.
+ * In the case false positive, it may contain SafeBrowsing
+ * matching list and provider of the list
+ * @return String the report phishing URL.
+ */
+ getReportURL(name, info) {
+ let reportInfo = info;
+ if (!reportInfo) {
+ let pageUri = getBrowser().currentURI.clone();
+
+ // Remove the query to avoid including potentially sensitive data
+ if (pageUri instanceof Ci.nsIURL) {
+ pageUri = pageUri.mutate().setQuery("").finalize();
+ }
+
+ reportInfo = { uri: pageUri.asciiSpec };
+ }
+ return SafeBrowsing.getReportURL(name, reportInfo);
+ },
+
+ initOverlay: function initOverlay(aEvent) {
+ var popup = document.getElementById("helpPopup");
+ popup.addEventListener("popupshowing", gSafeBrowsing.initMenuItems);
+ }
+}
+
+window.addEventListener("load", gSafeBrowsing.initOverlay);
diff --git a/comm/suite/browser/safeBrowsingOverlay.xul b/comm/suite/browser/safeBrowsingOverlay.xul
new file mode 100644
index 0000000000..e034fd5891
--- /dev/null
+++ b/comm/suite/browser/safeBrowsingOverlay.xul
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://communicator/locale/safeBrowsing.dtd">
+
+<overlay id="safeBrowsingOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://navigator/content/safeBrowsingOverlay.js"/>
+
+ <broadcasterset id="navBroadcasters">
+ <broadcaster id="safeBrowsingBroadcaster" disabled="true"/>
+ </broadcasterset>
+
+ <menupopup id="helpPopup">
+ <menuitem id="reportPhishing"
+ label="&reportDeceptiveSite.label;"
+ accesskey="&reportDeceptiveSite.accesskey;"
+ insertbefore="menu_HelpAboutSeparator"
+ observes="safeBrowsingBroadcaster"
+ oncommand="openUILink(gSafeBrowsing.getReportURL('Phish'), event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="reportPhishingError"
+ label="&notADeceptiveSite.label;"
+ accesskey="&notADeceptiveSite.accesskey;"
+ insertbefore="menu_HelpAboutSeparator"
+ observes="safeBrowsingBroadcaster"
+ oncommand="openUILinkIn(gSafeBrowsing.getReportURL('PhishMistake'), 'tabfocused');"/>
+ </menupopup>
+</overlay>
diff --git a/comm/suite/browser/sessionHistoryUI.js b/comm/suite/browser/sessionHistoryUI.js
new file mode 100644
index 0000000000..f8fdcc79aa
--- /dev/null
+++ b/comm/suite/browser/sessionHistoryUI.js
@@ -0,0 +1,172 @@
+/* -*- 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 {AppConstants} = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+function toggleTabsFromOtherComputers()
+ {
+ // enable/disable the Tabs From Other Computers menu
+ let menuitem = document.getElementById("sync-tabs-menuitem");
+
+ // If Sync isn't configured yet, then don't show the menuitem.
+ if (Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
+ Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
+ menuitem.hidden = true;
+ return;
+ }
+
+ // The tabs engine might never be inited (if services.sync.registerEngines
+ // is modified), so make sure we avoid undefined errors.
+ let enabled = Weave.Service.isLoggedIn &&
+ Weave.Service.engineManager.get("tabs") &&
+ Weave.Service.engineManager.get("tabs").enabled;
+ menuitem.setAttribute("disabled", !enabled);
+ menuitem.hidden = false;
+ }
+
+function FillHistoryMenu(aParent, aMenu)
+{
+ // Remove old entries if any
+ deleteHistoryItems(aParent);
+
+ var sessionHistory = getWebNavigation().sessionHistory;
+
+ var count = sessionHistory.count;
+ var index = sessionHistory.index;
+ var end;
+ const MAX_HISTORY_MENU_ITEMS = 15;
+
+ switch (aMenu)
+ {
+ case "back":
+ end = index > MAX_HISTORY_MENU_ITEMS ? index - MAX_HISTORY_MENU_ITEMS
+ : 0;
+ if (index <= end)
+ return false;
+ for (let j = index - 1; j >= end; j--)
+ {
+ let entry = sessionHistory.getEntryAtIndex(j);
+ if (entry)
+ createHistoryMenuItem(aParent, j, entry);
+ }
+ break;
+ case "forward":
+ end = count - index > MAX_HISTORY_MENU_ITEMS ? index + MAX_HISTORY_MENU_ITEMS
+ : count - 1;
+ if (index >= end)
+ return false;
+ for (let j = index + 1; j <= end; j++)
+ {
+ let entry = sessionHistory.getEntryAtIndex(j);
+ if (entry)
+ createHistoryMenuItem(aParent, j, entry);
+ }
+ break;
+ case "go":
+ var startHistory = document.getElementById("startHistorySeparator");
+ var endHistory = document.getElementById("endHistorySeparator");
+ // var syncMenuItem = document.getElementById("sync-tabs-menuitem");
+ startHistory.hidden = (count == 0);
+ end = count > MAX_HISTORY_MENU_ITEMS ? count - MAX_HISTORY_MENU_ITEMS
+ : 0;
+ for (let j = count - 1; j >= end; j--)
+ {
+ let entry = sessionHistory.getEntryAtIndex(j);
+ if (entry)
+ createHistoryMenuItem(aParent, j, entry, endHistory, j == index);
+ }
+ // toggleTabsFromOtherComputers();
+ // endHistory.hidden = (endHistory == aParent.lastChild || syncMenuItem.hidden);
+ endHistory.hidden = (endHistory == aParent.lastChild);
+ break;
+ }
+ return true;
+}
+
+function executeUrlBarHistoryCommand( aTarget )
+ {
+ var index = aTarget.getAttribute("index");
+ var label = aTarget.getAttribute("label");
+ if (index != "nothing_available" && label)
+ {
+ gURLBar.value = label;
+ UpdatePageProxyState();
+ handleURLBarCommand();
+ }
+ }
+
+function createUBHistoryMenu( aParent )
+ {
+ while (aParent.hasChildNodes())
+ aParent.lastChild.remove();
+
+ var file = GetUrlbarHistoryFile();
+ if (file.exists()) {
+ var connection = Services.storage.openDatabase(file);
+ try {
+ if (connection.tableExists("urlbarhistory")) {
+ var statement = connection.createStatement(
+ "SELECT url FROM urlbarhistory ORDER BY ROWID DESC");
+ while (statement.executeStep())
+ aParent.appendChild(document.createElement("menuitem"))
+ .setAttribute("label", statement.getString(0));
+ statement.reset();
+ statement.finalize();
+ return;
+ }
+ } finally {
+ connection.close();
+ }
+ }
+ //Create the "Nothing Available" Menu item and disable it.
+ var na = aParent.appendChild(document.createElement("menuitem"));
+ na.setAttribute("label", gNavigatorBundle.getString("nothingAvailable"));
+ na.setAttribute("disabled", "true");
+ }
+
+function createHistoryMenuItem(aParent, aIndex, aEntry, aAnchor, aChecked)
+{
+ var menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("label", aEntry.title);
+ menuitem.setAttribute("index", aIndex);
+ if (aChecked)
+ {
+ menuitem.setAttribute("type", "radio");
+ menuitem.setAttribute("checked", "true");
+ }
+
+ if (!aChecked || AppConstants.platform == "macosx")
+ {
+ menuitem.className = "menuitem-iconic bookmark-item menuitem-with-favicon";
+ PlacesUtils.favicons.getFaviconURLForPage(aEntry.URI,
+ function faviconURLCallback(aURI) {
+ if (aURI) {
+ menuitem.setAttribute("image",
+ PlacesUtils.favicons
+ .getFaviconLinkForIcon(aURI).spec);
+ }
+ }
+ );
+ }
+ aParent.insertBefore(menuitem, aAnchor);
+}
+
+function deleteHistoryItems(aParent)
+{
+ var children = aParent.childNodes;
+ for (let i = children.length - 1; i >= 0; --i)
+ {
+ if (children[i].hasAttribute("index"))
+ children[i].remove();
+ }
+}
+
+function updateGoMenu(event)
+ {
+ FillHistoryMenu(event.target, "go");
+ updateRecentMenuItems();
+ }
diff --git a/comm/suite/browser/tabbrowser.xml b/comm/suite/browser/tabbrowser.xml
new file mode 100644
index 0000000000..390fbaa0cf
--- /dev/null
+++ b/comm/suite/browser/tabbrowser.xml
@@ -0,0 +1,3707 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings [
+<!ENTITY % tabBrowserDTD SYSTEM "chrome://navigator/locale/tabbrowser.dtd" >
+%tabBrowserDTD;
+]>
+
+<bindings id="tabBrowserBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="tabbrowser">
+ <resources>
+ <stylesheet src="chrome://navigator/skin/tabbrowser.css"/>
+ </resources>
+
+ <content>
+ <xul:stringbundle anonid="tbstringbundle" src="chrome://navigator/locale/tabbrowser.properties"/>
+ <xul:tabbox anonid="tabbox" flex="1" eventnode="document">
+ <xul:hbox class="tab-drop-indicator-bar" collapsed="true">
+ <xul:image class="tab-drop-indicator" mousethrough="always"/>
+ </xul:hbox>
+ <xul:hbox class="tabbrowser-strip" collapsed="true" tooltip="_child" context="_child"
+ anonid="strip"
+ ondragstart="this.parentNode.parentNode._onDragStart(event);"
+ ondragover="this.parentNode.parentNode._onDragOver(event);"
+ ondrop="this.parentNode.parentNode._onDrop(event);"
+ ondragleave="this.parentNode.parentNode._onDragLeave(event);">
+ <xul:tooltip onpopupshowing="event.stopPropagation(); return this.parentNode.parentNode.parentNode.doPreview(this);"
+ onpopuphiding="this.parentNode.parentNode.parentNode.resetPreview(this);" orient="vertical">
+ <xul:label class="tooltip-label" crop="right"/>
+ <xul:label class="tooltip-label" hidden="true"><html:canvas class="tab-tooltip-canvas"/></xul:label>
+ </xul:tooltip>
+ <xul:menupopup anonid="tabContextMenu" onpopupshowing="return document.getBindingParent(this).updatePopupMenu(this);">
+ <xul:menuitem label="&closeTab.label;" accesskey="&closeTab.accesskey;"
+ tbattr="tabbrowser-tab"
+ oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
+ tabbrowser.removeTab(tabbrowser.mContextTab);"/>
+ <xul:menuitem label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
+ tbattr="tabbrowser-multiple tabbrowser-tab"
+ oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
+ tabbrowser.removeAllTabsBut(tabbrowser.mContextTab);"/>
+ <xul:menuitem label="&closeTabsToTheEnd.label;"
+ accesskey="&closeTabsToTheEnd.accesskey;"
+ tbattr="tabbrowser-totheend tabbrowser-tab"
+ oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
+ tabbrowser.removeTabsToTheEndFrom(tabbrowser.mContextTab);"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&newTab.label;" accesskey="&newTab.accesskey;"
+ xbl:inherits="oncommand=onnewtab"/>
+ <xul:menuitem label="&undoCloseTab.label;" accesskey="&undoCloseTab.accesskey;"
+ tbattr="tabbrowser-undoclosetab"
+ oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
+ tabbrowser.undoCloseTab(0);"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&bookmarkGroup.label;" accesskey="&bookmarkGroup.accesskey;"
+ tbattr="tabbrowser-multiple"
+ xbl:inherits="oncommand=onbookmarkgroup"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
+ tbattr="tabbrowser-tab"
+ oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
+ tabbrowser.reloadTab(tabbrowser.mContextTab);"/>
+ <xul:menuitem label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
+ tbattr="tabbrowser-multiple"
+ oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
+ tabbrowser.reloadAllTabs();"/>
+ </xul:menupopup>
+
+ <xul:tabs class="tabbrowser-tabs" flex="1"
+ anonid="tabcontainer"
+ tooltiptextnew="&newTabButton.tooltip;"
+ tooltiptextclose="&closeTabButton.tooltip;"
+ tooltiptextalltabs="&listAllTabs.tooltip;"
+ setfocus="false"
+ onclick="this.parentNode.parentNode.parentNode.onTabClick(event);"
+ xbl:inherits="onnewtab,onnewtabclick"
+ onclosetab="var node = this.parentNode;
+ while (node.localName != 'tabbrowser')
+ node = node.parentNode;
+ node.removeCurrentTab();">
+ <xul:tab selected="true" validate="never"
+ onerror="this.parentNode.parentNode.parentNode.parentNode.addToMissedIconCache(this.getAttribute('image'));
+ this.removeAttribute('image');"
+ width="0" flex="100"
+ class="tabbrowser-tab" label="&untitledTab;" crop="end"/>
+ </xul:tabs>
+ </xul:hbox>
+ <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
+ <xul:notificationbox class="browser-notificationbox" xbl:inherits="popupnotification">
+ <xul:stack flex="1" anonid="browserStack">
+ <xul:browser flex="1" type="content" primary="true"
+ xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,datetimepicker"/>
+ </xul:stack>
+ </xul:notificationbox>
+ </xul:tabpanels>
+ </xul:tabbox>
+ <children/>
+ </content>
+ <implementation implements="nsIDOMEventListener, nsIObserver">
+ <field name="closingTabsEnum" readonly="true">
+ ({ ALL: 0, OTHER: 1, TO_END: 2 });
+ </field>
+ <field name="mSessionStore" readonly="true">
+ Cc["@mozilla.org/suite/sessionstore;1"].getService(Ci.nsISessionStore);
+ </field>
+ <field name="mTabBox" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
+ </field>
+ <field name="mStrip" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "strip");
+ </field>
+ <field name="tabContainer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "tabcontainer");
+ </field>
+ <field name="mPanelContainer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
+ </field>
+ <field name="tabs" readonly="true">
+ this.tabContainer.childNodes
+ </field>
+ <field name="mStringBundle">
+ document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
+ </field>
+ <field name="mCurrentTab">
+ null
+ </field>
+ <field name="mPreviousTab">
+ null
+ </field>
+ <field name="mCurrentBrowser">
+ null
+ </field>
+ <field name="mProgressListeners">
+ []
+ </field>
+ <field name="mTabsProgressListeners">
+ []
+ </field>
+ <field name="mTabListeners">
+ new Array()
+ </field>
+ <field name="mTabFilters">
+ new Array()
+ </field>
+ <field name="mLastRelatedIndex">
+ 0
+ </field>
+ <field name="usePrivateBrowsing" readonly="true">
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext)
+ .usePrivateBrowsing;
+ </field>
+ <field name="mContextTab">
+ null
+ </field>
+
+ <method name="_handleKeyEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (!aEvent.isTrusted) {
+ // Don't let untrusted events mess with tabs.
+ return;
+ }
+
+ if (aEvent.altKey)
+ return;
+
+ if (AppConstants.platform == "macosx") {
+ if (!aEvent.metaKey)
+ return;
+
+ var offset = 1;
+ switch (aEvent.charCode) {
+ case '}'.charCodeAt(0):
+ offset = -1;
+ case '{'.charCodeAt(0):
+ if (window.getComputedStyle(this, null).direction == "ltr")
+ offset *= -1;
+ this.tabContainer.advanceSelectedTab(offset, true);
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ return;
+ }
+ } else {
+ if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
+ aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
+ this.getStripVisibility()) {
+ this.removeCurrentTab();
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ return;
+ }
+
+ if (aEvent.target == this) {
+ switch (aEvent.keyCode) {
+ case KeyEvent.DOM_VK_UP:
+ this.moveTabBackward();
+ break;
+ case KeyEvent.DOM_VK_DOWN:
+ this.moveTabForward();
+ break;
+ case KeyEvent.DOM_VK_RIGHT:
+ case KeyEvent.DOM_VK_LEFT:
+ this.moveTabOver(aEvent);
+ break;
+ case KeyEvent.DOM_VK_HOME:
+ this.moveTabToStart();
+ break;
+ case KeyEvent.DOM_VK_END:
+ this.moveTabToEnd();
+ break;
+ default:
+ // Stop the keypress event for the above keyboard
+ // shortcuts only.
+ return;
+ }
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ }
+ }
+ ]]></body>
+ </method>
+
+ <field name="arrowKeysShouldWrap">
+ null
+ </field>
+ <field name="nextTabNumber">
+ 0
+ </field>
+ <field name="_browsers">
+ null
+ </field>
+ <field name="savedBrowsers">
+ new Array()
+ </field>
+ <field name="referenceTab">
+ null
+ </field>
+
+ <method name="doPreview">
+ <parameter name="aPopup"/>
+ <body>
+ <![CDATA[
+ var tab = document.tooltipNode;
+ if (tab.localName != "tab")
+ return false;
+ var b = tab.linkedBrowser;
+ if (!b)
+ return false;
+
+ var label = aPopup.firstChild;
+ label.setAttribute("value", tab.getAttribute("label"));
+
+ var canvas = aPopup.lastChild.firstChild;
+ canvas.parentNode.hidden = true;
+
+ var win = b.contentWindow;
+ var w = win.innerWidth;
+ var h = win.innerHeight;
+
+ if (tab == this.mCurrentTab || h == 0 ||
+ !Services.prefs.getBoolPref("browser.tabs.tooltippreview.enable")) {
+ return true;
+ }
+
+ var ctx;
+ try {
+ ctx = canvas.getContext("2d");
+ } catch (e) {
+ return true;
+ }
+
+ label.width = 0;
+ aPopup.setAttribute("tabpreview", "true");
+
+ var canvasW = Services.prefs.getIntPref("browser.tabs.tooltippreview.width");
+ var canvasH = Math.round(canvasW * h / w);
+ canvas.width = canvasW;
+ canvas.height = canvasH;
+ canvas.parentNode.hidden = false;
+
+ var bgColor = Services.prefs.getBoolPref("browser.display.use_system_colors") ?
+ "Window" :
+ Services.prefs.getCharPref("browser.display.background_color");
+ if (b.contentDocument instanceof ImageDocument &&
+ !(b.contentDocument.imageRequest.imageStatus &
+ Ci.imgIRequest.STATUS_ERROR)) {
+ ctx.fillStyle = bgColor;
+ ctx.fillRect(0, 0, canvasW, canvasH);
+ var img = b.contentDocument.body.firstChild;
+ var ratio = img.naturalHeight / img.naturalWidth;
+ if (img.naturalHeight <= canvasH && img.naturalWidth <= canvasW) {
+ ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);
+ }
+ else if (ratio * canvasW > canvasH) {
+ ctx.drawImage(img, 0, 0, canvasH / ratio, canvasH);
+ }
+ else {
+ ctx.drawImage(img, 0, 0, canvasW, ratio * canvasW);
+ }
+ }
+ else {
+ ctx.save();
+ ctx.scale(canvasW / w, canvasH / h);
+ ctx.drawWindow(win, win.pageXOffset, win.pageYOffset, w, h, bgColor);
+ ctx.restore();
+ }
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <!-- XXXcst This should not be needed, but it seems that the tooltip
+ sizing is happening too early when we want to stop showing the
+ preview. This clears the label's width early (i.e. when the
+ previous preview disappears) so that when the next tooltip appears,
+ it doesn't start with a bad size. For now, I blame Gecko. -->
+ <method name="resetPreview">
+ <parameter name="aPopup"/>
+ <body>
+ <![CDATA[
+ var label = aPopup.firstChild;
+ // If this function is removed, these two lines need to be restored
+ // to the non-preview codepath above.
+ label.removeAttribute("width");
+ aPopup.removeAttribute("tabpreview");
+ ]]>
+ </body>
+ </method>
+
+ <method name="previewTab">
+ <parameter name="aTab"/>
+ <parameter name="aCallback"/>
+ <body>
+ <![CDATA[
+ let currentTab = this.selectedTab;
+ try {
+ // Suppress focus, ownership and selected tab changes.
+ this._previewMode = true;
+ this.selectedTab = aTab;
+ aCallback();
+ } finally {
+ this.selectedTab = currentTab;
+ this._previewMode = false;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserAtIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ return this.browsers[aIndex];
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserIndexForDocument">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ var browsers = this.browsers;
+ for (var i = 0; i < browsers.length; i++)
+ if (browsers[i].contentDocument == aDocument)
+ return i;
+ return -1;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForDocument">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ var browsers = this.browsers;
+ for (var i = 0; i < browsers.length; i++)
+ if (browsers[i].contentDocument == aDocument)
+ return browsers[i];
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForContentWindow">
+ <parameter name="aWindow"/>
+ <body>
+ <![CDATA[
+ const browsers = this.browsers;
+ for (let browser of browsers) {
+ if (browser.contentWindow == aWindow)
+ return browser;
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getNotificationBox">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return aBrowser ? aBrowser.parentNode.parentNode
+ : this.mCurrentBrowser.parentNode.parentNode;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_callProgressListeners">
+ <parameter name="aBrowser"/>
+ <parameter name="aMethod"/>
+ <parameter name="aArguments"/>
+ <parameter name="aCallGlobalListeners"/>
+ <parameter name="aCallTabsListeners"/>
+ <body><![CDATA[
+ if (!aBrowser)
+ aBrowser = this.mCurrentBrowser;
+
+ if (aCallGlobalListeners != false &&
+ aBrowser == this.mCurrentBrowser) {
+ this.mProgressListeners.forEach(function (p) {
+ if (aMethod in p) {
+ try {
+ p[aMethod].apply(p, aArguments);
+ } catch (e) {
+ // don't inhibit other listeners
+ Cu.reportError(e);
+ }
+ }
+ });
+ }
+
+ if (aCallTabsListeners != false) {
+ aArguments.unshift(aBrowser);
+
+ this.mTabsProgressListeners.forEach(function (p) {
+ if (aMethod in p) {
+ try {
+ p[aMethod].apply(p, aArguments);
+ } catch (e) {
+ // don't inhibit other listeners
+ Cu.reportError(e);
+ }
+ }
+ });
+ }
+ ]]></body>
+ </method>
+
+ <!-- A web progress listener object definition for a given tab. -->
+ <method name="mTabProgressListener">
+ <parameter name="aTab"/>
+ <parameter name="aBrowser"/>
+ <parameter name="aStartsBlank"/>
+ <body>
+ <![CDATA[
+ return ({
+ mTabBrowser: this,
+ mTab: aTab,
+ mBrowser: aBrowser,
+ mBlank: aStartsBlank,
+ mFeeds: [],
+ mRequest: null,
+ mStateFlags: 0,
+ mStatus: 0,
+ mMessage: "",
+
+ // cache flags for correct status UI update after tab switching
+ mTotalProgress: 0,
+
+ // count of open requests (should always be 0 or 1)
+ mRequestCount: 0,
+
+ _callProgressListeners: function () {
+ Array.prototype.unshift.call(arguments, this.mBrowser);
+ return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
+ },
+
+ _shouldShowProgress: function (aRequest) {
+ if (this.mBlank)
+ return false;
+
+ // Don't show progress indicators in tabs for about: URIs
+ // pointing to local resources.
+ if ((aRequest instanceof Ci.nsIChannel) &&
+ aRequest.originalURI.schemeIs("about") &&
+ (aRequest.URI.schemeIs("jar") || aRequest.URI.schemeIs("file")))
+ return false;
+
+ return true;
+ },
+
+ onProgressChange: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
+
+ if (!this._shouldShowProgress(aRequest))
+ return;
+
+ if (this.mTotalProgress && this.mTab.hasAttribute("busy"))
+ this.mTab.setAttribute("progress", "true");
+
+ this._callProgressListeners("onProgressChange",
+ [aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress]);
+ },
+
+ onProgressChange64: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ return this.onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ },
+
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (!aRequest)
+ return;
+
+ var oldBlank = this.mBlank;
+
+ let location;
+ let originalLocation;
+ try {
+ aRequest.QueryInterface(Ci.nsIChannel)
+ location = aRequest.URI;
+ originalLocation = aRequest.originalURI;
+ } catch (ex) {}
+
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
+ this.mRequestCount++;
+ }
+ else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ const NS_ERROR_UNKNOWN_HOST = 2152398878;
+ if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
+ // to prevent bug 235825: wait for the request handled
+ // by the automatic keyword resolver
+ return;
+ }
+ // since we (try to) only handle STATE_STOP of the last request,
+ // the count of open requests should now be 0
+ this.mRequestCount = 0;
+ }
+
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+ if (aWebProgress.isTopLevel) {
+ this.mFeeds = [];
+ // Need to use originalLocation rather than location because things
+ // like about:privatebrowsing arrive with nsIRequest pointing to
+ // their resolved jar: or file: URIs.
+ if (!(originalLocation && gInitialPages.has(originalLocation.spec) &&
+ originalLocation != "about:blank" &&
+ this.mBrowser.currentURI && this.mBrowser.currentURI.spec == "about:blank")) {
+ // This will trigger clearing the location bar. Don't do it if
+ // we loaded off a blank browser and this is an initial page load
+ // (e.g. about:privatebrowsing, etc.) so we avoid clearing the
+ // location bar in case the user is typing in it.
+ // Loading about:blank shouldn't trigger this, either, because its
+ // loads are "special".
+ this.mBrowser.urlbarChangeTracker.startedLoad();
+ }
+ // If the browser is loading it must not be crashed anymore.
+ this.mTab.removeAttribute("crashed");
+ }
+
+ if (this._shouldShowProgress(aRequest)) {
+ if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING) &&
+ aWebProgress && aWebProgress.isTopLevel) {
+ this.mTab.setAttribute("busy", "true");
+
+ // Do the following only for the top frame not any subframes.
+ // Remove favicon. This shows busy and progress indicators even during a reload.
+ this.mTab.removeAttribute("image");
+
+ if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
+ this.mTabBrowser.setTabTitleLoading(this.mTab);
+ }
+
+ if (this.mTab.selected)
+ this.mTabBrowser.mIsBusy = true;
+ }
+ }
+ else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+
+ if (this.mTab.hasAttribute("busy")) {
+ this.mTab.removeAttribute("busy");
+ this.mTabBrowser._tabAttrModified(this.mTab, ["busy"]);
+ if (!this.mTab.selected)
+ this.mTab.setAttribute("unread", "true");
+ }
+ this.mTab.removeAttribute("progress");
+
+ if (aWebProgress.isTopLevel) {
+ let isSuccessful = Components.isSuccessCode(aStatus);
+ if (!isSuccessful && !isTabEmpty(this.mTab)) {
+ // Restore the current document's location in case the
+ // request was stopped (possibly from a content script)
+ // before the location changed.
+ this.mBrowser.userTypedValue = null;
+
+ let inLoadURI = this.mBrowser.inLoadURI;
+ if (this.mTab.selected && gURLBar && !inLoadURI)
+ URLBarSetURI();
+ } else if (isSuccessful) {
+ this.mBrowser.urlbarChangeTracker.finishedLoad();
+ }
+
+ if (!this.mBrowser.mIconURL)
+ this.mTabBrowser.useDefaultIcon(this.mTab);
+ }
+
+ if (this.mBlank)
+ this.mBlank = false;
+
+ // For keyword URIs clear the user typed value since they will be changed into real URIs.
+ if (location && location.scheme == "keyword")
+ this.mBrowser.userTypedValue = null;
+
+ if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.loading"))
+ this.mTabBrowser.setTabTitle(this.mTab);
+
+ if (this.mTab.selected)
+ this.mTabBrowser.mIsBusy = false;
+ }
+
+ if (oldBlank) {
+ this._callProgressListeners("onUpdateCurrentBrowser",
+ [aStateFlags, aStatus, "", 0],
+ true, false);
+ } else {
+ this._callProgressListeners("onStateChange",
+ [aWebProgress, aRequest, aStateFlags, aStatus],
+ true, false);
+ }
+
+ this._callProgressListeners("onStateChange",
+ [aWebProgress, aRequest, aStateFlags, aStatus],
+ false);
+
+ if (aStateFlags & (Ci.nsIWebProgressListener.STATE_START |
+ Ci.nsIWebProgressListener.STATE_STOP)) {
+ // reset cached temporary values at beginning and end
+ this.mMessage = "";
+ this.mTotalProgress = 0;
+ }
+ this.mStateFlags = aStateFlags;
+ this.mStatus = aStatus;
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocation, aFlags) {
+ // OnLocationChange is called for both the top-level content
+ // and the subframes.
+ let topLevel = aWebProgress.isTopLevel;
+
+ if (topLevel) {
+ let isSameDocument =
+ !!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
+ // We need to clear the typed value
+ // if the document failed to load, to make sure the urlbar reflects the
+ // failed URI (particularly for SSL errors). However, don't clear the value
+ // if the error page's URI is about:blank, because that causes complete
+ // loss of urlbar contents for invalid URI errors (see bug 867957).
+ // Another reason to clear the userTypedValue is if this was an anchor
+ // navigation initiated by the user.
+ if (this.mBrowser.didStartLoadSinceLastUserTyping() ||
+ ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
+ aLocation.spec != "about:blank") ||
+ (isSameDocument && this.mBrowser.inLoadURI))
+ this.mBrowser.userTypedValue = null;
+
+ // Don't clear the favicon if this onLocationChange was
+ // triggered by a pushState or a replaceState (bug 550565) or
+ // a hash change (bug 408415).
+ if (aWebProgress.isLoadingDocument && !isSameDocument)
+ this.mBrowser.mIconURL = null;
+ }
+
+ if (!this.mBlank)
+ this._callProgressListeners("onLocationChange",
+ [aWebProgress, aRequest, aLocation,
+ aFlags]);
+
+ if (topLevel) {
+ this.mBrowser.lastURI = aLocation;
+ this.mBrowser.lastLocationChange = Date.now();
+ }
+ },
+
+ onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) {
+ if (this.mBlank)
+ return;
+
+ this.mMessage = aMessage;
+
+ this.mTabBrowser._callProgressListeners(this.mBrowser, "onStatusChange",
+ [aWebProgress, aRequest, aStatus, aMessage]);
+ },
+
+ onSecurityChange: function (aWebProgress, aRequest, aState) {
+ this.mTabBrowser._callProgressListeners(this.mBrowser, "onSecurityChange",
+ [aWebProgress, aRequest, aState]);
+ },
+
+ onRefreshAttempted: function(aWebProgress, aURI, aDelay, aSameURI)
+ {
+ var allowRefresh = true;
+ if (this.mTabBrowser.mCurrentTab == this.mTab) {
+ this.mTabBrowser.mProgressListeners.forEach(
+ function notifyRefreshAttempted(element) {
+ if (element && "onRefreshAttempted" in element) {
+ try {
+ if (!element.onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI))
+ allowRefresh = false;
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ }
+ );
+ }
+
+ this.mTabBrowser.mTabsProgressListeners.forEach(
+ function notifyRefreshAttempted(element) {
+ if (element && "onRefreshAttempted" in element) {
+ try {
+ if (!element.onRefreshAttempted(this.mBrowser, aWebProgress, aURI, aDelay, aSameURI))
+ allowRefresh = false;
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ }
+ , this);
+ return allowRefresh;
+ },
+
+ addFeed: function(aLink)
+ {
+ this.mFeeds.push(aLink);
+ },
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsIWebProgressListener2) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ }
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="mInstallSH">
+ <parameter name="aBrowser"/>
+ <parameter name="aSH"/>
+ <body>
+ <![CDATA[
+ return ({
+ mBrowser: aBrowser,
+ mSH: aSH,
+
+ onProgressChange : function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress)
+ {
+ },
+
+ onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if ((aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) &&
+ (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP)) {
+ function refresh(closure) {
+ closure.mBrowser.webNavigation.sessionHistory = closure.mSH;
+ closure.mBrowser.webProgress.removeProgressListener(closure);
+ delete closure.mBrowser._SHListener;
+ closure.mSH.QueryInterface(Ci.nsIWebNavigation)
+ .gotoIndex(closure.mSH.index);
+ }
+ setTimeout(refresh, 0, this);
+ }
+ },
+
+ onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ },
+
+ onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ },
+
+ onSecurityChange : function(aWebProgress, aRequest, aState)
+ {
+ },
+
+ QueryInterface : function(aIID)
+ {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ }
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="setIcon">
+ <parameter name="aTab"/>
+ <parameter name="aURI"/>
+ <parameter name="aLoadingPrincipal"/>
+ <body>
+ <![CDATA[
+ let browser = this.getBrowserForTab(aTab);
+ browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
+
+ if (aURI) {
+ if (!(aURI instanceof Ci.nsIURI)) {
+ aURI = makeURI(aURI);
+ }
+
+ // We do not serialize the principal from within nsSessionStore.js,
+ // hence if aLoadingPrincipal is null we default to the
+ // systemPrincipal which will allow the favicon to load.
+ let loadingPrincipal = aLoadingPrincipal ||
+ Services.scriptSecurityManager.getSystemPrincipal();
+
+ PlacesUIUtils.loadFavicon(browser, loadingPrincipal, aURI);
+ }
+
+ let sizedIconUrl = browser.mIconURL || "";
+ if (sizedIconUrl != aTab.getAttribute("image")) {
+ if (sizedIconUrl)
+ aTab.setAttribute("image", sizedIconUrl);
+ else
+ aTab.removeAttribute("image");
+ this._tabAttrModified(aTab, ["image"]);
+ }
+
+ this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getIcon">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ let browser = aTab ? aTab.linkedBrowser : this.selectedBrowser;
+ return browser.mIconURL;
+ ]]>
+ </body>
+ </method>
+
+ <method name="buildFavIconString">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ try {
+ aURI = Services.uriFixup.createExposableURI(aURI);
+ } catch (e) {
+ }
+ return aURI.resolve("/favicon.ico");
+ ]]>
+ </body>
+ </method>
+
+ <method name="shouldLoadFavIcon">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ try {
+ aURI = Services.uriFixup.createExposableURI(aURI);
+ } catch (e) {
+ }
+ return (aURI && Services.prefs.getBoolPref("browser.chrome.site_icons") &&
+ Services.prefs.getBoolPref("browser.chrome.favicons") &&
+ ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
+ ]]>
+ </body>
+ </method>
+
+ <method name="useDefaultIcon">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var browser = this.getBrowserForTab(aTab);
+ var documentURI = browser.documentURI;
+ var icon = null;
+
+ if (browser.imageDocument) {
+ if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
+ let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
+ if (browser.imageDocument.width <= sz &&
+ browser.imageDocument.height <= sz) {
+ icon = browser.currentURI;
+ }
+ }
+ }
+
+ // Use documentURIObject in the check for shouldLoadFavIcon so that we
+ // do the right thing with about:-style error pages. Bug 453442
+ if (!icon && this.shouldLoadFavIcon(documentURI)) {
+ let url = documentURI.prePath + "/favicon.ico";
+ if (!this.isFailedIcon(url))
+ icon = url;
+ }
+ this.setIcon(aTab, icon, browser.contentPrincipal);
+ ]]>
+ </body>
+ </method>
+
+ <method name="isFailedIcon">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ if (!(aURI instanceof Ci.nsIURI))
+ aURI = makeURI(aURI);
+ return PlacesUtils.favicons.isFailedFavicon(aURI);
+ ]]>
+ </body>
+ </method>
+
+ <method name="loadFavIcon">
+ <parameter name="aURI"/>
+ <parameter name="aAttr"/>
+ <parameter name="aElt"/>
+ <parameter name="aLoadingPrincipal"/>
+ <body>
+ <![CDATA[
+ let iconURL = this.buildFavIconString(aURI);
+ let iconURI = Services.io.newURI(iconURL);
+ let faviconFlags = this.usePrivateBrowsing ?
+ PlacesUtils.favicons.FAVICON_LOAD_PRIVATE
+ : PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE;
+ let loadingPrincipal = aLoadingPrincipal ||
+ Services.scriptSecurityManager.getSystemPrincipal();
+
+ PlacesUtils.favicons
+ .setAndFetchFaviconForPage(aURI, iconURI, false, faviconFlags,
+ null,
+ loadingPrincipal);
+ if (PlacesUtils.favicons.isFailedFavicon(aURI)) {
+ return;
+ }
+
+ aElt.setAttribute(aAttr, iconURL);
+ ]]>
+ </body>
+ </method>
+
+ <method name="addToMissedIconCache">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ let uri = Services.io.newURI(aURI);
+ PlacesUtils.favicons.addFailedFavicon(uri);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTitleForURI">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ try {
+ aURI = Services.uriFixup.createExposableURI(aURI).spec;
+ } catch (e) {
+ aURI = aURI.spec;
+ }
+
+ if (aURI == "about:blank")
+ return "";
+
+ // We have a URI. Let's try to unescape it using a character set
+ // in case the URI is not ASCII.
+ try {
+ let characterSet = this.mCurrentBrowser.contentDocument.characterSet;
+ let textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"]
+ .getService(Ci.nsITextToSubURI);
+ aURI = textToSubURI.unEscapeNonAsciiURI(characterSet, aURI);
+ } catch (e) {
+ // Do nothing.
+ }
+ return aURI;
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateUrlBar">
+ <parameter name="aWebProgress"/>
+ <parameter name="aRequest"/>
+ <parameter name="aLocation"/>
+ <parameter name="aFlags"/>
+ <parameter name="aSecurityUI"/>
+ <parameter name="aBrowser"/>
+ <parameter name="aFeeds"/>
+ <body>
+ <![CDATA[
+ this.mProgressListeners.forEach(
+ function notifyUrlBar(element) {
+ try {
+ if ("onLocationChange" in element)
+ element.onLocationChange(aWebProgress, aRequest, aLocation, aFlags);
+ // If switching tabs, the security may have changed.
+ if (aSecurityUI && "onSecurityChange" in element)
+ element.onSecurityChange(aWebProgress, null, aSecurityUI.state);
+ // If the document already exists, just resend cached data.
+ if (!aRequest && aWebProgress.isTopLevel) {
+ if (aBrowser.mIconURL && "onLinkIconAvailable" in element)
+ element.onLinkIconAvailable(aBrowser.mIconURL);
+ if ("onFeedAvailable" in element) {
+ aFeeds.forEach(
+ function notifyFeedAvailable(feed) {
+ element.onFeedAvailable(feed);
+ }
+ );
+ }
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ );
+ ]]>
+ </body>
+ </method>
+
+ <method name="getWindowTitleForBrowser">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ var newTitle = "";
+ var docTitle;
+ var docElement = this.ownerDocument.documentElement;
+ var sep = docElement.getAttribute("titlemenuseparator");
+ var modifier = docElement.getAttribute("titlemodifier");
+
+ // Strip out any null bytes in the content title, since the
+ // underlying widget implementations of nsWindow::SetTitle pass
+ // null-terminated strings to system APIs.
+ if (aBrowser.docShell.contentViewer)
+ docTitle = aBrowser.contentTitle.replace(/\0/g, "");
+
+ if (!docTitle && !modifier) {
+ docTitle = this.getTitleForURI(aBrowser.currentURI);
+ if (!docTitle) {
+ // Here we actually override contenttitlesetting, because we
+ // don't want the titledefault value.
+ docTitle = this.mStringBundle.getString("tabs.untitled");
+ }
+ }
+
+ if (docTitle) {
+ newTitle += docElement.getAttribute("titlepreface") + docTitle;
+ if (modifier)
+ newTitle += sep;
+ }
+ newTitle += modifier;
+
+ // If location bar is hidden and the URL type supports a host,
+ // add the scheme and host to the title to prevent spoofing.
+ // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
+ // (only for schemes that support a host)
+ try {
+ if (docElement.getAttribute("chromehidden").includes("location")) {
+ let uri = Services.uriFixup.createExposableURI(
+ aBrowser.currentURI);
+ if (uri.schemeIs("about"))
+ newTitle = uri.spec + sep + newTitle;
+ else if (uri.host)
+ newTitle = uri.prePath + sep + newTitle;
+ }
+ } catch (e) {
+ }
+
+ return newTitle;
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateTitlebar">
+ <body>
+ <![CDATA[
+ var newTitle = this.getWindowTitleForBrowser(this.mCurrentBrowser);
+ document.title = newTitle;
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIBaseWindow).title = newTitle;
+ ]]>
+ </body>
+ </method>
+
+ <method name="updatePopupMenu">
+ <parameter name="aPopupMenu"/>
+ <body>
+ <![CDATA[
+ this.mContextTab = aPopupMenu.triggerNode;
+ // The user might right-click on a tab or an empty part of the tabbar.
+ var isTab = this.mContextTab.localName == "tab";
+ var isMultiple = this.tabs.length > 1;
+ var isAtEnd = this.getTabsToTheEndFrom(this.mContextTab).length == 0;
+ var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "*");
+ for (let menuitem of menuItems) {
+ let tbattr = menuitem.getAttribute("tbattr");
+
+ if (tbattr.includes("tabbrowser-undoclosetab")) {
+ menuitem.disabled = (this.usePrivateBrowsing ?
+ this.savedBrowsers.length :
+ this.mSessionStore.getClosedTabCount(window)) == 0;
+ menuitem.hidden = (this.usePrivateBrowsing ||
+ Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo") <= 0) &&
+ Services.prefs.getIntPref("browser.tabs.max_tabs_undo") <= 0;
+ }
+ else
+ menuitem.disabled =
+ (tbattr.includes("tabbrowser-totheend") && isAtEnd) ||
+ (tbattr.includes("tabbrowser-multiple") && !isMultiple) ||
+ (tbattr.includes("tabbrowser-tab") && !isTab);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="mAeroPeek">false</field>
+
+ <method name="updateCurrentBrowser">
+ <body>
+ <![CDATA[
+ var newBrowser = this.mPanelContainer.selectedPanel.firstChild.firstChild;
+ var oldBrowser = this.mCurrentBrowser;
+
+ // Transfer the dropped link handler to the new browser.
+ // Note: closing the current tab sets mCurrentBrowser to null
+ // so we use mCurrentTab.linkedBrowser instead.
+ newBrowser.droppedLinkHandler = this.mCurrentTab.linkedBrowser.droppedLinkHandler;
+ newBrowser.showWindowResizer = this.mCurrentTab.linkedBrowser.showWindowResizer;
+ newBrowser.docShellIsActive = this.mCurrentTab.linkedBrowser.docShellIsActive;
+ if (this.mCurrentBrowser) {
+ this.mCurrentBrowser.droppedLinkHandler = null;
+ this.mCurrentBrowser.docShellIsActive = false;
+ this.mCurrentBrowser.removeAttribute("primary");
+ this.finder.mListeners.forEach(l => this.mCurrentBrowser.finder.removeResultListener(l));
+ }
+
+ let oldTab = this.mCurrentTab;
+
+ // Preview mode should not reset the owner.
+ if (!this._previewMode && !oldTab.selected)
+ oldTab.owner = null;
+
+ let lastRelatedTab = this.mLastRelatedIndex ? this.tabs[this.mLastRelatedIndex] : null;
+ if (lastRelatedTab && !lastRelatedTab.selected) {
+ lastRelatedTab.owner = null;
+ }
+
+ newBrowser.setAttribute("primary", "true");
+ this.mCurrentBrowser = newBrowser;
+ this.mCurrentTab = this.selectedTab;
+ this.mCurrentTab.removeAttribute("unread");
+ this.finder.mListeners.forEach(l => this.mCurrentBrowser.finder.addResultListener(l));
+
+ var tabListener = this.mTabListeners[this.tabContainer.selectedIndex];
+
+ if (!oldBrowser ||
+ (!oldBrowser.blockedPopups != !newBrowser.blockedPopups))
+ this.mCurrentBrowser.updateBlockedPopups();
+
+ // Update the URL bar.
+ this.updateUrlBar(newBrowser.webProgress,
+ null,
+ newBrowser.currentURI,
+ 0,
+ newBrowser.securityUI,
+ newBrowser,
+ tabListener.mFeeds);
+
+ // Send the state, status and progress to all progress listeners.
+ var flags = tabListener.mStateFlags &
+ (Ci.nsIWebProgressListener.STATE_START |
+ Ci.nsIWebProgressListener.STATE_STOP);
+ this._callProgressListeners(null, "onStateChange",
+ [this.mCurrentBrowser.webProgress,
+ tabListener.mRequest,
+ flags,
+ tabListener.mStatus],
+ true, false);
+
+ this._callProgressListeners(null, "onStatusChange",
+ [this.mCurrentBrowser.webProgress,
+ tabListener.mRequest,
+ tabListener.mStatus,
+ tabListener.mMessage],
+ true, false);
+
+ // Also send the onUpdateCurrentBrowser event for compatibility
+ this._callProgressListeners(null, "onUpdateCurrentBrowser",
+ [tabListener.mStateFlags,
+ tabListener.mStatus,
+ tabListener.mMessage,
+ tabListener.mTotalProgress],
+ true, false);
+
+ if (this.mAeroPeek)
+ return;
+
+ // we only want to return to the parent tab if no other
+ // tabs have been opened and the user hasn't switched tabs
+ this.mPreviousTab = null;
+ this.mLastRelatedIndex = 0;
+
+ // Update the window title.
+ this.updateTitlebar();
+
+ // FAYT
+ this.fastFind.setDocShell(this.mCurrentBrowser.docShell);
+
+ // We've selected the new tab, so go ahead and notify listeners
+ this.mCurrentTab.dispatchEvent(new Event("TabSelect",
+ { bubbles: true, cancelable: false }));
+
+ if (!document.commandDispatcher.focusedElement ||
+ document.commandDispatcher.focusedElement.parentNode !=
+ this.mCurrentTab.parentNode) {
+ // The focus was not on one of our tabs, so focus the new browser.
+ newBrowser.focus();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="onTabClick">
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ // A middle mouse button click on a tab is a short cut for
+ // closing that tab.
+ if (event.button != 1 || event.target.localName != 'tab')
+ return;
+
+ this.removeTab(event.target);
+ event.stopPropagation();
+ event.preventDefault();
+ ]]>
+ </body>
+ </method>
+
+ <method name="onLinkEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ var link = aEvent.originalTarget;
+ var href = link.href;
+ if (!href)
+ return;
+
+ var targetDoc = link.ownerDocument;
+ var index = this.getBrowserIndexForDocument(targetDoc);
+ if (index < 0)
+ return;
+
+ var rel = link.rel;
+ var type = link.type;
+ var isIcon = /(?:^|\s)icon(?:\s|$)/i.test(rel) &&
+ Services.prefs.getBoolPref("browser.chrome.site_icons");
+ if (isIcon) {
+ var iconUri = this.getLinkIconURI(link);
+ if (iconUri)
+ this.setIcon(this.tabs[index], iconUri,
+ link.nodePrincipal);
+ return;
+ }
+
+ if (aEvent.type == "DOMLinkChanged")
+ return;
+
+ var isFeed = /(?:^|\s)feed(?:\s|$)/i.test(rel) ||
+ (/(?:^|\s)alternate(?:\s|$)/i.test(rel) &&
+ !/(?:^|\s)stylesheet(?:\s|$)/i.test(rel) &&
+ /^\s*application\/(?:atom|rss)\+xml\s*$/i.test(type));
+
+ if (!isFeed)
+ return;
+
+ try {
+ let feedURI = Services.io.newURI(href, targetDoc.characterSet);
+ if (!/^https?$/.test(feedURI.scheme)) {
+ return;
+ }
+ urlSecurityCheck(feedURI, targetDoc.nodePrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ } catch(e) {
+ return;
+ }
+
+ this.mTabListeners[index].addFeed(link);
+ if (this.browsers[index] == this.mCurrentBrowser) {
+ this.mProgressListeners.forEach(
+ function notifyFeedAvailable(element) {
+ if ("onFeedAvailable" in element) {
+ try {
+ element.onFeedAvailable(link);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ }
+ );
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="getLinkIconURI">
+ <parameter name="aLink"/>
+ <body><![CDATA[
+ var targetDoc = aLink.ownerDocument;
+ // Make a URI out of our href.
+ var uri = Services.io.newURI(aLink.href, targetDoc.characterSet);
+
+ // Verify that the load of this icon is legal.
+ // Some error or special pages can load their favicon.
+ // To be on the safe side, only allow chrome:// favicons.
+ const re = /^about:(neterror|certerror|blocked)\?/;
+ var isAllowedPage = re.test(targetDoc.documentURI);
+
+ if (!isAllowedPage || !uri.schemeIs("chrome")) {
+ try {
+ urlSecurityCheck(uri,targetDoc.nodePrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ } catch(e) {
+ return null;
+ }
+ }
+
+ // Security says okay, now ask content policy
+ try {
+ var contentPolicy =
+ Cc['@mozilla.org/layout/content-policy;1']
+ .getService(Ci.nsIContentPolicy);
+ } catch (e) {
+ return null; // Refuse to load if we can't do a security check.
+ }
+
+ if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE,
+ uri, targetDoc.documentURIObject,
+ aLink, aLink.type,
+ null) != Ci.nsIContentPolicy.ACCEPT) {
+ return null;
+ }
+ return uri;
+ ]]></body>
+ </method>
+
+ <method name="_tabAttrModified">
+ <parameter name="aTab"/>
+ <parameter name="aChanged"/>
+ <body><![CDATA[
+ if (aTab.closing)
+ return;
+
+ let event = new CustomEvent("TabAttrModified", {
+ bubbles: true,
+ cancelable: false,
+ detail: {
+ changed: aChanged,
+ }
+ });
+ aTab.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="setTabTitleLoading">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ aTab.label = this.mStringBundle.getString("tabs.loading");
+ aTab.crop = "end";
+ this._tabAttrModified(aTab, ["label", "crop"]);
+ ]]>
+ </body>
+ </method>
+
+ <method name="setTabTitle">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var browser = aTab.linkedBrowser;
+ var title = browser.contentTitle;
+ var crop = "end";
+
+ if (!title) {
+ title = this.getTitleForURI(browser.currentURI);
+
+ if (title)
+ crop = "center";
+ else
+ title = this.mStringBundle.getString("tabs.untitled");
+ }
+ aTab.label = title;
+ aTab.crop = crop;
+ ]]>
+ </body>
+ </method>
+
+ <method name="setStripVisibilityTo">
+ <parameter name="aShow"/>
+ <body>
+ <![CDATA[
+ this.mStrip.collapsed = !aShow;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getStripVisibility">
+ <body>
+ return !this.mStrip.collapsed;
+ </body>
+ </method>
+
+ <method name="loadOneTab">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <parameter name="aLoadInBackground"/>
+ <parameter name="aAllowThirdPartyFixup"/>
+ <body>
+ <![CDATA[
+ var params = aReferrerURI;
+ if (!params || params instanceof Ci.nsIURI) {
+ params = {
+ triggeringPrincipal: Services.scriptSecurityManager
+ .getSystemPrincipal(),
+ referrerURI: aReferrerURI,
+ charset: aCharset,
+ postData: aPostData,
+ inBackground: aLoadInBackground,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ allowMixedContent: false,
+ userContextId: null,
+ opener: null,
+ };
+ }
+
+ params.focusNewTab = params.inBackground != null ?
+ !params.inBackground :
+ !Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+
+ if (params.focusNewTab)
+ params.ownerTab = this.selectedTab;
+
+ return this.addTab(aURI, params);
+ ]]>
+ </body>
+ </method>
+
+ <method name="loadTabs">
+ <parameter name="aURIs"/>
+ <parameter name="aLoadInBackground"/>
+ <parameter name="aReplace"/>
+ <body><![CDATA[
+ let aAllowThirdPartyFixup;
+ let aPostDatas = [];
+ let aUserContextId;
+ let aTriggeringPrincipal;
+
+ // Additional parameters are in a params object.
+ // Firefox uses additional parameters not supported here.
+ if (arguments.length == 2 &&
+ typeof arguments[1] == "object") {
+ let params = arguments[1];
+ aLoadInBackground = params.inBackground;
+ aReplace = params.replace;
+ aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ aPostDatas = params.postDatas || aPostDatas;
+ aUserContextId = params.userContextId;
+ aTriggeringPrincipal = params.triggeringPrincipal;
+ }
+
+ if (!aURIs.length)
+ return;
+
+ // The tab selected after this new tab is closed (i.e. the new tab's
+ // "owner") is the next adjacent tab (i.e. not the previously viewed tab)
+ // when several urls are opened here (i.e. closing the first should select
+ // the next of many URLs opened) or if the pref to have UI links opened in
+ // the background is set (i.e. the link is not being opened modally)
+ //
+ // i.e.
+ // Number of URLs Load UI Links in BG Focus Last Viewed?
+ // == 1 false YES
+ // == 1 true NO
+ // > 1 false/true NO
+ var multiple = aURIs.length > 1;
+ var owner = multiple || aLoadInBackground ? null : this.selectedTab;
+ var firstTabAdded = null;
+ var targetTabIndex = -1;
+
+ if (aReplace) {
+ let browser;
+ browser = this.mCurrentBrowser;
+ targetTabIndex = this.tabContainer.selectedIndex;
+
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (aAllowThirdPartyFixup) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
+ Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+ try {
+ browser.loadURIWithFlags(aURIs[0], {
+ flags,
+ postData: aPostDatas[0],
+ triggeringPrincipal : aTriggeringPrincipal,
+ });
+ } catch (e) {
+ // Ignore failure in case a URI is wrong, so we can continue
+ // opening the next ones.
+ }
+ } else {
+ firstTabAdded = this.addTab(aURIs[0], {
+ ownerTab: owner,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostDatas[0],
+ userContextId: aUserContextId,
+ triggeringPrincipal: aTriggeringPrincipal,
+ });
+ }
+
+ let tabNum = targetTabIndex;
+ for (let i = 1; i < aURIs.length; ++i) {
+ let tab = this.addTab(aURIs[i], {
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostDatas[i],
+ userContextId: aUserContextId,
+ triggeringPrincipal: aTriggeringPrincipal,
+ });
+ if (targetTabIndex !== -1)
+ this.moveTabTo(tab, ++tabNum);
+ }
+
+ if (!aLoadInBackground) {
+ if (firstTabAdded) {
+ // .selectedTab setter focuses the content area
+ this.selectedTab = firstTabAdded;
+ } else
+ this.selectedBrowser.focus();
+ }
+ ]]></body>
+ </method>
+
+ <method name="addTab">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <parameter name="aFocusNewTab"/>
+ <parameter name="aAllowThirdPartyFixup"/>
+ <body>
+ <![CDATA[
+ var aTriggeringPrincipal;
+ var aReferrerPolicy;
+ var aFromExternal;
+ var aOwner;
+ var aRelatedToCurrent;
+ var aAllowMixedContent;
+ var aNoReferrer;
+ var aUserContextId;
+ var aOriginPrincipal;
+ var aOpener;
+ if (arguments.length == 2 &&
+ arguments[1] != null &&
+ typeof arguments[1] == "object" &&
+ !(arguments[1] instanceof Ci.nsIURI)) {
+ let params = arguments[1];
+ aTriggeringPrincipal = params.triggeringPrincipal;
+ aReferrerURI = params.referrerURI;
+ aReferrerPolicy = params.referrerPolicy;
+ aCharset = params.charset;
+ aPostData = params.postData;
+ aOwner = params.ownerTab;
+ aFocusNewTab = params.focusNewTab;
+ aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ aFromExternal = params.fromExternal;
+ aRelatedToCurrent = params.relatedToCurrent;
+ aAllowMixedContent = params.allowMixedContent;
+ aNoReferrer = params.noReferrer;
+ aUserContextId = params.userContextId;
+ aOriginPrincipal = params.originPrincipal;
+ aOpener = params.opener;
+ }
+
+ // If we're adding tabs, we're past interrupt mode, ditch the owner.
+ if (this.mCurrentTab.owner)
+ this.mCurrentTab.owner = null;
+
+ this._browsers = null; // invalidate cache
+
+ var t = this.referenceTab.cloneNode(true);
+
+ var blank = !aURI || aURI == "about:blank";
+
+ if (!blank)
+ t.setAttribute("label", aURI);
+
+ this.tabContainer.appendChild(t);
+
+ var b = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "browser");
+ b.setAttribute("type", "content");
+ b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
+ b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
+ b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
+ if (this.hasAttribute("datetimepicker")) {
+ b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
+ }
+
+ // Check if we have a "parent" window which we need to set as our opener
+ if (aOpener) {
+ b.presetOpenerWindow(aOpener);
+ }
+
+ // Create the browserStack container
+ var stack = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "stack");
+ stack.setAttribute("anonid", "browserStack");
+ stack.appendChild(b);
+ stack.setAttribute("flex", "1");
+
+ // Add the Message and the Browser to the box
+ var n = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "notificationbox");
+ n.setAttribute("class", "browser-notificationbox");
+ n.setAttribute("popupnotification", this.getAttribute("popupnotification"));
+ n.appendChild(stack);
+
+ var uniqueId = "panel" + this.nextTabNumber++;
+ n.id = uniqueId;
+ t.linkedPanel = uniqueId;
+ t.linkedBrowser = b;
+ if (t.previousSibling.selected)
+ t.setAttribute("afterselected", true);
+
+ // Prevent the superfluous initial load of a blank document
+ // if we're going to load something other than about:blank.
+ if (!blank)
+ b.setAttribute("nodefaultsrc", "true");
+
+ // NB: this appendChild call causes us to run constructors for the
+ // browser element, which fires off a bunch of notifications. Some
+ // of those notifications can cause code to run that inspects our
+ // state, so it is important that the tab element is fully
+ // initialized by this point.
+ this.mPanelContainer.appendChild(n);
+
+ // We start our browsers out as inactive.
+ b.docShellIsActive = false;
+
+ this.mStrip.collapsed = false;
+
+ Services.prefs.setBoolPref("browser.tabs.forceHide", false);
+
+ // If this new tab is owned by another, assert that relationship.
+ if (aOwner)
+ t.owner = aOwner;
+
+ // wire up a progress listener for the new browser object.
+ var position = this.tabs.length - 1;
+ var tabListener = this.mTabProgressListener(t, b, blank);
+ const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Ci.nsIWebProgress);
+ filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
+ b.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
+ this.mTabListeners[position] = tabListener;
+ this.mTabFilters[position] = filter;
+
+ if (!blank) {
+ // pretend the user typed this so it'll be available till
+ // the document successfully loads
+ b.userTypedValue = aURI;
+
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (aAllowThirdPartyFixup)
+ flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
+ Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ if (aFromExternal)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
+ if (aAllowMixedContent)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
+ try {
+ b.loadURIWithFlags(aURI, {
+ flags,
+ triggeringPrincipal : aTriggeringPrincipal,
+ referrerURI: aNoReferrer ? null : aReferrerURI,
+ charset: aCharset,
+ referrerPolicy: aReferrerPolicy,
+ postData: aPostData,
+ });
+ }
+ catch (ex) { }
+ }
+
+ t.dispatchEvent(new Event("TabOpen",
+ { bubbles: true, cancelable: false }));
+
+ // Check if we're opening a tab related to the current tab and
+ // move it to after the current tab.
+ // aReferrerURI is null or undefined if the tab is opened from
+ // an external application or bookmark, i.e. somewhere other
+ // than the current tab.
+ if ((aRelatedToCurrent || aReferrerURI ||
+ Services.prefs.getBoolPref("browser.tabs.insertAllTabsAfterCurrent")) &&
+ Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
+ var lastRelatedIndex = this.mLastRelatedIndex ||
+ this.tabContainer.selectedIndex;
+ if (this.mLastRelatedIndex)
+ this.tabs[this.mLastRelatedIndex].owner = null;
+ else
+ t.owner = this.selectedTab;
+ this.moveTabTo(t, ++lastRelatedIndex);
+ this.mLastRelatedIndex = lastRelatedIndex;
+ }
+
+ if (aFocusNewTab) {
+ var parentTab = this.selectedTab;
+ this.selectedTab = t;
+ this.mPreviousTab = parentTab;
+ }
+ else {
+ // The user opened a background tab, so updateCurrentBrowser
+ // won't be called. Explicitly clear the previous tab.
+ this.mPreviousTab = null;
+ }
+ this.tabContainer._handleNewTab(t);
+
+ return t;
+ ]]>
+ </body>
+ </method>
+
+ <method name="warnAboutClosingTabs">
+ <parameter name="aCloseTabs"/>
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var tabsToClose;
+ switch (aCloseTabs) {
+ case this.closingTabsEnum.ALL:
+ tabsToClose = this.tabs.length;
+ break;
+ case this.closingTabsEnum.OTHER:
+ tabsToClose = this.tabs.length - 1;
+ break;
+ case this.closingTabsEnum.TO_END:
+ if (!aTab)
+ throw new Error("Required argument missing: aTab");
+
+ tabsToClose = this.getTabsToTheEndFrom(aTab).length;
+ break;
+ default:
+ throw new Error("Invalid argument: " + aCloseTabs);
+ }
+
+ if (tabsToClose <= 1)
+ return true;
+
+ const pref = aCloseTabs == this.closingTabsEnum.ALL ?
+ "browser.tabs.warnOnClose" :
+ "browser.tabs.warnOnCloseOther";
+ if (!Services.prefs.getBoolPref(pref))
+ return true;
+
+ //default to true: if it were false, we wouldn't get this far
+ var warnOnClose = { value:true };
+ var bundle = this.mStringBundle;
+
+ // Focus the window before prompting. This will raise any minimized
+ // window, which will make it obvious which window the prompt is
+ // for and will solve the problem of windows "obscuring" the
+ // prompt. See bug #350299 for more details.
+ window.focus();
+ var warningTitle;
+ var warningMessage;
+ var closeButton;
+ var promptMessage;
+ switch (aCloseTabs) {
+ case this.closingTabsEnum.ALL:
+ warningTitle = "tabs.closeWarningTitleAll";
+ warningMessage =
+ PluralForm.get(tabsToClose,
+ bundle.getString("tabs.closeWarningAll"));
+ closeButton = "tabs.closeButtonAll";
+ promptMessage = "tabs.closeWarningPromptMeAll";
+ break;
+ case this.closingTabsEnum.OTHER:
+ // fall through
+ case this.closingTabsEnum.TO_END:
+ // fall through
+ default:
+ warningTitle = "tabs.closeWarningTitle";
+ warningMessage =
+ PluralForm.get(tabsToClose,
+ bundle.getString("tabs.closeWarningOther"));
+ closeButton = "tabs.closeButton";
+ promptMessage = "tabs.closeWarningPromptMe";
+ break;
+ }
+
+ var ps = Services.prompt;
+ var buttonPressed =
+ ps.confirmEx(window,
+ bundle.getString(warningTitle),
+ warningMessage.replace("#1", tabsToClose),
+ (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
+ + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
+ bundle.getString(closeButton),
+ null, null,
+ bundle.getString(promptMessage),
+ warnOnClose);
+ var reallyClose = (buttonPressed == 0);
+ // Don't set the pref unless they press OK and it's false
+ if (reallyClose && !warnOnClose.value)
+ Services.prefs.setBoolPref(pref, false);
+
+ return reallyClose;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabsToTheEndFrom">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ let tabsToEnd = [];
+ let tabs = this.tabs;
+ for (let i = tabs.length - 1; tabs[i] != aTab && i >= 0; --i) {
+ tabsToEnd.push(tabs[i]);
+ }
+ return tabsToEnd.reverse();
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeTabsToTheEndFrom">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
+ let tabs = this.getTabsToTheEndFrom(aTab);
+ for (let i = tabs.length - 1; i >= 0; --i) {
+ this.removeTab(tabs[i]);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeAllTabsBut">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
+ this.selectedTab = aTab;
+
+ for (let i = this.tabs.length - 1; i >= 0; --i) {
+ if (this.tabs[i] != aTab)
+ this.removeTab(this.tabs[i]);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeCurrentTab">
+ <parameter name="aParams"/>
+ <body>
+ <![CDATA[
+ return this.removeTab(this.mCurrentTab, aParams);
+ ]]>
+ </body>
+ </method>
+
+ <method name="isBrowserEmpty">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return aBrowser.sessionHistory.count < 2 &&
+ aBrowser.currentURI.spec == "about:blank" &&
+ !aBrowser.contentDocument.body.hasChildNodes();
+ ]]>
+ </body>
+ </method>
+
+ <method name="getUndoList">
+ <body>
+ <![CDATA[
+ var tabData = this.usePrivateBrowsing ? this.savedBrowsers :
+ JSON.parse(this.mSessionStore.getClosedTabData(window));
+ return tabData.map(function(aTabData) { return aTabData.title; });
+ ]]>
+ </body>
+ </method>
+
+ <method name="undoCloseTab">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ if (this.usePrivateBrowsing)
+ return this.savedBrowsers.length ? this.restoreTab(aIndex) : null;
+
+ return this.mSessionStore.getClosedTabCount(window) ?
+ this.mSessionStore.undoCloseTab(window, aIndex) : null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="restoreTab">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ if (aIndex >= this.savedBrowsers.length || aIndex < 0)
+ return null;
+
+ this._browsers = null;
+
+ var savedData = this.savedBrowsers.splice(aIndex, 1)[0];
+ var t = savedData.browserData.tab;
+ var b = savedData.browserData.browser;
+ var hist = savedData.browserData.history;
+
+ this.tabContainer.appendChild(t);
+ if (t.previousSibling.selected)
+ t.setAttribute("afterselected", true);
+
+ // navigate back to the proper page from the light page
+ b.stop();
+ b.webNavigation.gotoIndex(0);
+
+ // reattach the old history
+ b.webNavigation.sessionHistory = hist;
+
+ // add back the filters, security first (bug 313335)
+ var secFlags = Ci.nsIWebProgress.NOTIFY_STATE_ALL |
+ Ci.nsIWebProgress.NOTIFY_LOCATION |
+ Ci.nsIWebProgress.NOTIFY_SECURITY;
+ b.webProgress.addProgressListener(b.securityUI, secFlags);
+
+ var position = this.tabs.length - 1;
+ var tabListener = this.mTabProgressListener(t, b, false);
+ const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Ci.nsIWebProgress);
+ filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
+ b.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
+ this.mTabListeners[position] = tabListener;
+ this.mTabFilters[position] = filter;
+
+ t.dispatchEvent(new Event("TabOpen",
+ { bubbles: true, cancelable: false }));
+
+ if (savedData.pos < position)
+ this.moveTabTo(t, savedData.pos);
+
+ if (this.tabs.length == 2 && this.isBrowserEmpty(this))
+ this.removeCurrentTab({ disableUndo: true });
+ else {
+ this.selectedTab = t;
+ this.mStrip.collapsed = false;
+ }
+ this.tabContainer._handleNewTab(t);
+
+ return t;
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeBrowser">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ let panel = this.getNotificationBox(aBrowser);
+ panel.destroy();
+ aBrowser.destroy();
+
+ // The pagehide event that this removal triggers is safe
+ // because the browser is no longer current at this point.
+ panel.remove();
+
+ // Fix up the selected panel.
+ panel = this.getNotificationBox(this.selectedTab.linkedBrowser);
+ this.mTabBox.selectedPanel = panel;
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeTab">
+ <parameter name="aTab"/>
+ <parameter name="aParams"/>
+ <body>
+ <![CDATA[
+ this.mLastRelatedIndex = 0;
+
+ if (!aParams) {
+ aParams = {
+ animate: false,
+ disableUndo: false
+ };
+ }
+
+ var oldBrowser = aTab.linkedBrowser;
+
+ var ds = oldBrowser.docShell;
+
+ if (ds.contentViewer && !ds.contentViewer.permitUnload())
+ return;
+
+ // We're committed to closing the tab now.
+ var l = this.tabs.length;
+ switch (l) {
+ case 1:
+ // add a new blank tab to replace the one we're about to close
+ // (this ensures that the remaining tab is as good as new)
+ this.addTab("about:blank");
+ l++;
+ // fall through
+ case 2:
+ if (Services.prefs.getBoolPref("browser.tabs.autoHide"))
+ this.mStrip.collapsed = true;
+ }
+
+ // Dispatch a notification.
+ // We dispatch it before any teardown so that event listeners can
+ // inspect the tab that's about to close.
+ aTab.dispatchEvent(new UIEvent("TabClose",
+ { bubbles: true, cancelable: false, view: window,
+ detail: !!aParams.disableUndo }));
+ var tabData = aTab.tabData || {};
+ tabData.pos = this.getTabIndex(aTab);
+ tabData.panel = this.getNotificationBox(oldBrowser).id;
+ tabData.title = oldBrowser.contentDocument.title ||
+ this.getTitleForURI(oldBrowser.currentURI) ||
+ this.mStringBundle.getString("tabs.untitled");
+
+ var index = this.getTabIndex(aTab);
+
+ // Remove SSL listener
+ oldBrowser.webProgress.removeProgressListener(oldBrowser.securityUI);
+
+ // Remove the tab's filter and progress listener.
+ const filter = this.mTabFilters[index];
+ oldBrowser.webProgress.removeProgressListener(filter);
+ filter.removeProgressListener(this.mTabListeners[index]);
+ this.mTabFilters.splice(index, 1);
+ this.mTabListeners.splice(index, 1);
+
+ // We are no longer the primary content area
+ oldBrowser.removeAttribute("primary");
+
+ // Remove this tab as the owner of any other tabs, since it's going away.
+ for (let tab of this.tabs) {
+ if ("owner" in tab && tab.owner == aTab)
+ // |tab| is a child of the tab we're removing, make it an orphan.
+ tab.owner = null;
+ }
+
+ // Now select the new tab before nuking the old one.
+ var currentIndex = this.tabContainer.selectedIndex;
+
+ var newIndex = -1;
+ if (currentIndex > index)
+ newIndex = currentIndex - 1;
+ else if (currentIndex < index)
+ newIndex = currentIndex;
+ else if (index == l - 1)
+ newIndex = index - 1;
+ else
+ newIndex = index;
+
+ if (oldBrowser == this.mCurrentBrowser)
+ this.mCurrentBrowser = null;
+
+ // Invalidate browsers cache, as the tab is removed from the
+ // tab container.
+ this._browsers = null;
+
+ let owner = ("owner" in aTab) ? aTab.owner : null;
+
+ // Clean up before/after selected attributes before removing the
+ // tab.
+ aTab._selected = false;
+ aTab.remove();
+
+ // When the current tab is removed select a new tab
+ // and fire select events on tabpanels and tabs
+ if (owner && !owner.hidden && !owner.closing &&
+ Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
+ this.selectedTab = owner;
+ }
+ else if (this.mPreviousTab && (aTab == this.mCurrentTab))
+ this.selectedTab = this.mPreviousTab;
+ else {
+ this.tabContainer.selectedIndex = newIndex;
+
+ // We need to explicitly clear this, because updateCurrentBrowser
+ // doesn't get called for a background tab
+ this.mPreviousTab = null;
+ }
+
+ // Save the tab for undo.
+ // Even though we navigate to about:blank, it costs more RAM than
+ // really closing the tab. The pref controls how far you can undo
+ var maxUndoDepth = Services.prefs.getIntPref("browser.tabs.max_tabs_undo");
+ var oldSH = oldBrowser.webNavigation.sessionHistory;
+ var inOnLoad = oldBrowser.docShell.isExecutingOnLoadHandler;
+ var isPopup = oldBrowser.contentWindow.opener &&
+ !Services.prefs.getBoolPref("browser.tabs.cache_popups");
+ if (maxUndoDepth <= 0 || aParams.disableUndo || inOnLoad || isPopup || this.isBrowserEmpty(oldBrowser)) {
+ // Undo is disabled/tab is blank. Kill the browser for real.
+ // Because of the way XBL works (fields just set JS
+ // properties on the element) and the code we have in place
+ // to preserve the JS objects for any elements that have
+ // JS properties set on them, the browser element won't be
+ // destroyed until the document goes away. So we force a
+ // cleanup ourselves. Also fix up the selected panel in the case
+ // the removed browser was to the left of the current browser.
+ this.removeBrowser(oldBrowser);
+ return;
+ }
+
+ // preserve a pointer to the browser for undoing the close
+ // 1. save a copy of the session history (oldSH)
+ // 2. hook up a new history
+ // 3. add the last history entry from the old history the new
+ // history so we'll be able to go back from about:blank
+ // 4. load a light URL in the browser, pushing the current page
+ // into bfcache - allows for saving of JS modifications
+ // and also saves RAM by allowing bfcache to evict the full page
+
+ tabData.browserData = {
+ tab: aTab,
+ browser: oldBrowser,
+ history: oldSH,
+ toJSON: function() {} // hides this object from JSON.stringify
+ };
+ this.savedBrowsers.unshift(tabData);
+
+ var newSH = Cc["@mozilla.org/browser/shistory;1"]
+ .createInstance(Ci.nsISHistory);
+ oldBrowser.webNavigation.sessionHistory = newSH;
+ var entry = oldSH.getEntryAtIndex(oldSH.index)
+ .QueryInterface(Ci.nsISHEntry)
+ .clone();
+ // The bfcache entry is tightly coupled to the original shistory it
+ // belongs to, better to drop it.
+ entry.abandonBFCacheEntry();
+ // don't try to repost data when restoring the tab
+ entry.postData = null;
+ newSH.addEntry(entry, true);
+
+ // about:blank is light
+ oldBrowser.loadURI("about:blank");
+
+ // remove overflow from the undo stack
+ if (this.savedBrowsers.length > maxUndoDepth) {
+ tabData = this.savedBrowsers.pop();
+ var deadBrowser = tabData.browserData.browser;
+ delete tabData.browserData;
+ this.removeBrowser(deadBrowser);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="forgetSavedBrowser">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ if (aIndex >= this.savedBrowsers.length || aIndex < 0)
+ return false;
+
+ var tabData = this.savedBrowsers.splice(aIndex, 1)[0];
+ var deadBrowser = tabData.browserData.browser;
+ delete tabData.browserData;
+ this.removeBrowser(deadBrowser);
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadAllTabs">
+ <body>
+ <![CDATA[
+ var l = this.tabs.length;
+ for (var i = 0; i < l; i++) {
+ try {
+ this.tabs[i].linkedBrowser.reload();
+ } catch (e) {
+ // ignore failure to reload so others will be reloaded
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ this.getBrowserForTab(aTab).reload();
+ ]]>
+ </body>
+ </method>
+
+ <method name="addProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ if (!aListener)
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ if (this.mProgressListeners.includes(aListener))
+ throw Cr.NS_ERROR_FAILURE;
+
+ // push() does not disturb possibly ongoing iterations.
+ this.mProgressListeners.push(aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ if (!this.mProgressListeners.includes(aListener))
+ throw Cr.NS_ERROR_FAILURE;
+
+ // Create a new array, not to disturb possibly ongoing iterations.
+ this.mProgressListeners =
+ this.mProgressListeners.filter(
+ function removeListener(element) {
+ return element != aListener;
+ }
+ );
+ ]]>
+ </body>
+ </method>
+
+ <method name="addTabsProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ if (!aListener)
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ if (this.mTabsProgressListeners.includes(aListener))
+ throw Cr.NS_ERROR_FAILURE;
+
+ // push() does not disturb possibly ongoing iterations.
+ this.mTabsProgressListeners.push(aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeTabsProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ if (!this.mTabsProgressListeners.includes(aListener))
+ throw Cr.NS_ERROR_FAILURE;
+
+ // Create a new array, not to disturb possibly ongoing iterations.
+ this.mTabsProgressListeners =
+ this.mTabsProgressListeners.filter(
+ function removeListener(element) {
+ return element != aListener;
+ }
+ );
+ ]]>
+ </body>
+ </method>
+
+ <method name="_getTabForContentWindow">
+ <parameter name="aWindow"/>
+ <body>
+ <![CDATA[
+ const browsers = this.browsers;
+ for (var i = 0; i < browsers.length; ++i)
+ if (browsers[i].contentWindow == aWindow)
+ return this.tabs[i];
+
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabForBrowser">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ for (var tab of this.tabs)
+ if (tab.linkedBrowser == aBrowser)
+ return tab;
+
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ return aTab.linkedBrowser;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForOuterWindowID">
+ <parameter name="aID"/>
+ <body>
+ <![CDATA[
+ for (var browser of this.browsers)
+ if (browser.outerWindowID == aID)
+ return browser;
+
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabIndex">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ for (var i = 0; i < this.tabs.length; ++i)
+ if (this.tabs[i] == aTab)
+ return i;
+
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ ]]>
+ </body>
+ </method>
+
+ <property name="popupAnchor" readonly="true">
+ <getter><![CDATA[
+ if (this.mCurrentTab._popupAnchor) {
+ return this.mCurrentTab._popupAnchor;
+ }
+ // Actually the notificationbox not the browserstack.
+ let stack = this.mCurrentBrowser.parentNode;
+ // Create an anchor for the popup
+ const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let popupAnchor = document.createElementNS(NS_XUL, "hbox");
+ popupAnchor.className = "popup-anchor";
+ popupAnchor.hidden = true;
+ stack.appendChild(popupAnchor);
+ return this.mCurrentTab._popupAnchor = popupAnchor;
+ ]]></getter>
+ </property>
+
+ <method name="selectTabAtIndex">
+ <parameter name="aIndex"/>
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ // count backwards for aIndex < 0
+ if (aIndex < 0)
+ aIndex += this.tabs.length;
+
+ if (aIndex >= 0 &&
+ aIndex < this.tabs.length &&
+ aIndex != this.tabContainer.selectedIndex)
+ this.selectedTab = this.tabs[aIndex];
+
+ if (aEvent) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="selectedTab">
+ <getter>
+ return this.mTabBox.selectedTab;
+ </getter>
+ <setter>
+ <![CDATA[
+ // Update the tab
+ this.mTabBox.selectedTab = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedBrowser"
+ onget="return this.mCurrentBrowser;"
+ readonly="true"/>
+
+ <property name="browsers" readonly="true">
+ <getter>
+ <![CDATA[
+ return this._browsers ||
+ (this._browsers = Array.from(this.tabs, tab => tab.linkedBrowser));
+ ]]>
+ </getter>
+ </property>
+
+ <!-- Drag and drop observer API -->
+ <method name="_onDragStart">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ var target = aEvent.target;
+ if (target.localName == "tab") {
+ var URI = target.linkedBrowser.currentURI;
+ var spec = URI.spec;
+ var title = target.linkedBrowser.contentTitle || spec;
+ var dt = aEvent.dataTransfer;
+ dt.mozSetDataAt("text/x-moz-url", spec + "\n" + title, 0);
+ dt.mozSetDataAt("text/uri-list", spec, 0);
+ dt.mozSetDataAt("text/plain", spec, 0);
+ dt.mozSetDataAt("text/html", '<a href="' + spec + '">' + title + '</a>', 0);
+ }
+ aEvent.stopPropagation();
+ ]]>
+ </body>
+ </method>
+
+ <method name="_onDragOver">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+
+ var ib = document.getAnonymousElementByAttribute(this, "class", "tab-drop-indicator-bar");
+
+ // autoscroll the tab strip if we drag over the scroll buttons,
+ // even if we aren't dragging a tab
+ var pixelsToScroll = 0;
+ var tabStrip = this.tabContainer.arrowScrollbox;
+ var ltr = window.getComputedStyle(this, null).direction == "ltr";
+ if (this.tabContainer.getAttribute("overflow") == "true") {
+ var targetAnonid = aEvent.originalTarget.getAttribute("anonid");
+ switch (targetAnonid) {
+ case "scrollbutton-up":
+ pixelsToScroll = -tabStrip.scrollIncrement;
+ break;
+ case "scrollbutton-down":
+ case "alltabs-button":
+ pixelsToScroll = tabStrip.scrollIncrement;
+ break;
+ }
+ if (pixelsToScroll)
+ tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
+ }
+
+ var ind = document.getAnonymousElementByAttribute(this, "class", "tab-drop-indicator");
+
+ var draggedTab = aEvent.dataTransfer.mozSourceNode;
+ var within = draggedTab &&
+ draggedTab.parentNode == this.tabContainer;
+ var newIndexOn = within ? -1 : this.getDropOnIndex(aEvent);
+
+ var ltr = window.getComputedStyle(this, null).direction == "ltr";
+ var arrowX, tabBoxObject;
+ if (newIndexOn != -1) {
+ tabBoxObject = this.tabs[newIndexOn].boxObject;
+ arrowX = tabBoxObject.screenX + tabBoxObject.width / 2;
+ }
+ else {
+ var newIndexBetween = this.getDropIndex(aEvent);
+ if (within) {
+ var tabIndex = this.getTabIndex(draggedTab);
+ if (newIndexBetween == tabIndex ||
+ newIndexBetween == tabIndex + 1) {
+ ib.collapsed = true;
+ return;
+ }
+ }
+
+ if (newIndexBetween == this.tabs.length) {
+ tabBoxObject = this.tabs[this.tabs.length - 1].boxObject;
+ arrowX = tabBoxObject.x;
+ arrowX = tabBoxObject.screenX;
+ if (ltr) // for LTR "after" is on the right-hand side of the tab
+ arrowX += tabBoxObject.width;
+ }
+ else {
+ tabBoxObject = this.tabs[newIndexBetween].boxObject;
+ arrowX = tabBoxObject.screenX;
+ if (!ltr) // for RTL "before" is on the right-hand side of the tab
+ arrowX += tabBoxObject.width;
+ }
+ }
+
+ var boxObject = tabStrip.scrollBoxObject;
+ // Check pixelsToScroll as well to prevent noticable judder.
+ if (pixelsToScroll > 0 || arrowX >= boxObject.screenX + boxObject.width)
+ arrowX = boxObject.screenX + boxObject.width;
+ else if (pixelsToScroll < 0 || arrowX < boxObject.screenX)
+ arrowX = boxObject.screenX;
+
+ if (ltr)
+ ind.style.marginLeft = (arrowX - this.boxObject.screenX) + "px";
+ else
+ ind.style.marginRight = (this.boxObject.screenX + this.boxObject.width - arrowX) + "px";
+
+ ib.collapsed = false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_onDrop">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ document.getAnonymousElementByAttribute(this, "class",
+ "tab-drop-indicator-bar")
+ .collapsed = true;
+ aEvent.stopPropagation();
+
+ var newIndex = this.getDropIndex(aEvent);
+ var dt = aEvent.dataTransfer;
+ var draggedTab = dt.mozSourceNode;
+ if (draggedTab && draggedTab.parentNode == this.tabContainer) {
+ if (newIndex > this.getTabIndex(draggedTab))
+ newIndex--;
+ this.moveTabTo(draggedTab, newIndex);
+ return;
+ }
+
+ var url;
+ try {
+ // Pass true to disallow dropping javascript: or data: urls.
+ url = Services.droppedLinkHandler.dropLink(aEvent, {}, true);
+ } catch (ex) {}
+
+ // Valid urls don't contain spaces ' '; if we have a space
+ // it isn't a valid url.
+ if (!url || url.includes(" "))
+ return;
+
+ getShortcutOrURIAndPostData(url).then(data => {
+ var bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+ if (aEvent.shiftKey)
+ bgLoad = !bgLoad;
+
+ let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
+
+ var tab = null;
+ tabIndex = this.getDropOnIndex(aEvent);
+ if (tabIndex != -1) {
+ // Load in an existing tab.
+ tab = this.tabs[tabIndex];
+ tab.linkedBrowser.loadURI(data.url, {
+ allowThirdPartyFixup: true,
+ triggeringPrincipal,
+ });
+ if (this.mCurrentTab != tab && !bgLoad)
+ this.selectedTab = tab;
+ }
+ else if (dt.mozSourceDocument &&
+ dt.mozSourceDocument.defaultView.top == content) {
+ // We're adding a new tab, and we may want parent-tab tracking.
+
+ tab = this.loadOneTab(data.url, {
+ inBackground: bgLoad,
+ allowThirdPartyFixup: true,
+ triggeringPrincipal,
+ });
+
+ this.moveTabTo(tab, newIndex);
+ }
+ else {
+ // We're adding a new tab, but do not want parent-tab tracking.
+ tab = this.addTab(data.url, {
+ allowThirdPartyFixup: true,
+ triggeringPrincipal,
+ });
+
+ this.moveTabTo(tab, newIndex);
+ if (this.mCurrentTab != tab && !bgLoad)
+ this.selectedTab = tab;
+ }
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="_onDragLeave">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ var target = aEvent.relatedTarget;
+ while (target && target != this.mStrip)
+ target = target.parentNode;
+
+ if (target)
+ return;
+
+ document.getAnonymousElementByAttribute(this, "class",
+ "tab-drop-indicator-bar")
+ .collapsed = true;
+ aEvent.stopPropagation();
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabTo">
+ <parameter name="aTab"/>
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ let oldPosition;
+ // for compatibility with extensions
+ if (typeof(aTab) == "number") {
+ oldPosition = aTab;
+ aTab = this.tabs[oldPosition];
+ } else {
+ oldPosition = this.getTabIndex(aTab);
+ }
+
+ if (oldPosition == aIndex)
+ return;
+
+ this.mLastRelatedIndex = 0;
+
+ this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(oldPosition, 1)[0]);
+ this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(oldPosition, 1)[0]);
+
+ let wasFocused = (document.activeElement == this.mCurrentTab);
+
+ if (aIndex >= oldPosition)
+ ++aIndex;
+ this.mCurrentTab._selected = false;
+
+ // invalidate cache
+ this._browsers = null;
+
+ // Use .item() instead of [] because dragging to the end of the
+ // strip goes out of bounds: .item() returns null (so it acts like
+ // appendChild), but [] throws.
+ var tab = this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
+
+ this.mCurrentTab._selected = true;
+
+ if (wasFocused)
+ this.mCurrentTab.focus();
+
+ this.tabContainer._handleTabSelect(false);
+
+ tab.dispatchEvent(new UIEvent("TabMove",
+ { bubbles: true, cancelable: false, view: window,
+ detail: oldPosition }));
+ ]]>
+ </body>
+ </method>
+
+ <method name="getDropIndex">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ for (var i = 0; i < this.tabs.length; ++i) {
+ var coord = this.tabs[i].boxObject.screenX +
+ this.tabs[i].boxObject.width / 2;
+ if (window.getComputedStyle(this, null).direction == "ltr") {
+ if (aEvent.screenX < coord)
+ return i;
+ } else {
+ if (aEvent.screenX > coord)
+ return i;
+ }
+ }
+
+ return this.tabs.length;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getDropOnIndex">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ for (var i = 0; i < this.tabs.length; ++i) {
+ var tabBoxObject = this.tabs[i].boxObject;
+ if (aEvent.screenX > tabBoxObject.screenX + tabBoxObject.width * .25 &&
+ aEvent.screenX < tabBoxObject.screenX + tabBoxObject.width * .75)
+ return i;
+ }
+
+ return -1;
+ ]]>
+ </body>
+ </method>
+
+ <!-- moveTabLeft and moveTabRight methods have been kept for backwards
+ compatibility for extensions. Internally moveTabOver is used. -->
+ <method name="moveTabLeft">
+ <body>
+ <![CDATA[
+ if (window.getComputedStyle(this, null).direction == "ltr")
+ this.moveTabBackward();
+ else
+ this.moveTabForward();
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabRight">
+ <body>
+ <![CDATA[
+ if (window.getComputedStyle(this, null).direction == "ltr")
+ this.moveTabForward();
+ else
+ this.moveTabBackward();
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabForward">
+ <body>
+ <![CDATA[
+ var tabPos = this.tabContainer.selectedIndex;
+ if (tabPos < this.browsers.length - 1) {
+ this.moveTabTo(this.mCurrentTab, tabPos + 1);
+ }
+ else if (this.arrowKeysShouldWrap)
+ this.moveTabToStart();
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabBackward">
+ <body>
+ <![CDATA[
+ var tabPos = this.tabContainer.selectedIndex;
+ if (tabPos > 0) {
+ this.moveTabTo(this.mCurrentTab, tabPos - 1);
+ }
+ else if (this.arrowKeysShouldWrap)
+ this.moveTabToEnd();
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabToStart">
+ <body>
+ <![CDATA[
+ if (this.tabContainer.selectedIndex > 0) {
+ this.moveTabTo(this.mCurrentTab, 0);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabToEnd">
+ <body>
+ <![CDATA[
+ if (this.tabContainer.selectedIndex < this.browsers.length - 1) {
+ this.moveTabTo(this.mCurrentTab, this.browsers.length - 1);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabOver">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ var direction = window.getComputedStyle(this, null).direction;
+ var keyCode = aEvent.keyCode;
+ if ((direction == "ltr" && keyCode == KeyEvent.DOM_VK_RIGHT) ||
+ (direction == "rtl" && keyCode == KeyEvent.DOM_VK_LEFT))
+ this.moveTabForward();
+ else
+ this.moveTabBackward();
+ ]]>
+ </body>
+ </method>
+
+ <!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
+ MAKE SURE TO ADD IT HERE AS WELL. -->
+ <property name="canGoBack"
+ onget="return this.mCurrentBrowser.canGoBack;"
+ readonly="true"/>
+
+ <property name="canGoForward"
+ onget="return this.mCurrentBrowser.canGoForward;"
+ readonly="true"/>
+
+ <method name="goBack">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goBack();
+ ]]>
+ </body>
+ </method>
+
+ <method name="goForward">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goForward();
+ ]]>
+ </body>
+ </method>
+
+ <method name="reload">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.reload();
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadWithFlags">
+ <parameter name="aFlags"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.reloadWithFlags(aFlags);
+ ]]>
+ </body>
+ </method>
+
+ <method name="stop">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.stop();
+ ]]>
+ </body>
+ </method>
+
+ <!-- throws exception for unknown schemes -->
+ <method name="loadURI">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
+ ]]>
+ </body>
+ </method>
+
+ <!-- throws exception for unknown schemes -->
+ <method name="loadURIWithFlags">
+ <parameter name="aURI"/>
+ <parameter name="aFlags"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <body>
+ <![CDATA[
+ // Note - the callee understands both:
+ // (a) loadURIWithFlags(aURI, aFlags, ...)
+ // (b) loadURIWithFlags(aURI, { flags: aFlags, ... })
+ // Forwarding it as (a) here actually supports both (a) and (b),
+ // so you can call us either way too.
+ return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
+ ]]>
+ </body>
+ </method>
+
+ <method name="goHome">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goHome();
+ ]]>
+ </body>
+ </method>
+
+ <property name="homePage">
+ <getter>
+ <![CDATA[
+ return this.mCurrentBrowser.homePage;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this.mCurrentBrowser.homePage = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="gotoIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.gotoIndex(aIndex);
+ ]]>
+ </body>
+ </method>
+
+ <property name="currentURI"
+ onget="return this.mCurrentBrowser.currentURI;"
+ readonly="true"/>
+
+ <field name="finder"><![CDATA[
+ ({
+ mTabBrowser: this,
+ mListeners: new Set(),
+ get finder() {
+ return this.mTabBrowser.mCurrentBrowser.finder;
+ },
+ addResultListener: function(aListener) {
+ this.mListeners.add(aListener);
+ this.finder.addResultListener(aListener);
+ },
+ removeResultListener: function(aListener) {
+ this.mListeners.delete(aListener);
+ this.finder.removeResultListener(aListener);
+ },
+ get searchString() {
+ return this.finder.searchString;
+ },
+ get clipboardSearchString() {
+ return this.finder.clipboardSearchString;
+ },
+ set clipboardSearchString(val) {
+ return this.finder.clipboardSearchString = val;
+ },
+ set caseSensitive(val) {
+ return this.finder.caseSensitive = val;
+ },
+ set entireWord(val) {
+ return this.finder.entireWord = val;
+ },
+ get highlighter() {
+ return this.finder.highlighter;
+ },
+ get matchesCountLimit() {
+ return this.finder.matchesCountLimit;
+ },
+ fastFind: function(aSearchString, aLinksOnly, aDrawOutline) {
+ this.finder.fastFind(aSearchString, aLinksOnly, aDrawOutline);
+ },
+ findAgain: function(aFindBackwards, aLinksOnly, aDrawOutline) {
+ this.finder.findAgain(aFindBackwards, aLinksOnly, aDrawOutline);
+ },
+ setSearchStringToSelection: function() {
+ return this.finder.setSearchStringToSelection();
+ },
+ highlight: function(...args) {
+ this.finder.highlight(...args);
+ },
+ getInitialSelection: function() {
+ this.finder.getInitialSelection();
+ },
+ getActiveSelectionText: function() {
+ return this.finder.getActiveSelectionText();
+ },
+ enableSelection: function() {
+ this.finder.enableSelection();
+ },
+ removeSelection: function() {
+ this.finder.removeSelection();
+ },
+ focusContent: function() {
+ this.finder.focusContent();
+ },
+ onFindbarClose: function() {
+ this.finder.onFindbarClose();
+ },
+ onFindbarOpen: function() {
+ this.finder.onFindbarOpen();
+ },
+ onModalHighlightChange: function(...args) {
+ return this.finder.onModalHighlightChange(...args);
+ },
+ onHighlightAllChange: function(...args) {
+ return this.finder.onHighlightAllChange(...args);
+ },
+ keyPress: function(aEvent) {
+ this.finder.keyPress(aEvent);
+ },
+ requestMatchesCount: function(...args) {
+ this.finder.requestMatchesCount(...args);
+ }
+ })
+ ]]></field>
+
+ <property name="docShell"
+ onget="return this.mCurrentBrowser.docShell"
+ readonly="true"/>
+
+ <property name="webNavigation"
+ onget="return this.mCurrentBrowser.webNavigation"
+ readonly="true"/>
+
+ <property name="webBrowserFind"
+ readonly="true"
+ onget="return this.mCurrentBrowser.webBrowserFind"/>
+
+ <property name="webProgress"
+ readonly="true"
+ onget="return this.mCurrentBrowser.webProgress"/>
+
+ <property name="contentWindow"
+ readonly="true"
+ onget="return this.mCurrentBrowser.contentWindow"/>
+
+ <property name="contentWindowAsCPOW"
+ readonly="true"
+ onget="return this.mCurrentBrowser.contentWindowAsCPOW"/>
+
+ <property name="sessionHistory"
+ onget="return this.mCurrentBrowser.sessionHistory;"
+ readonly="true"/>
+
+ <property name="markupDocumentViewer"
+ onget="return this.mCurrentBrowser.markupDocumentViewer;"
+ readonly="true"/>
+
+ <property name="contentDocument"
+ onget="return this.mCurrentBrowser.contentDocument;"
+ readonly="true"/>
+
+ <property name="contentTitle"
+ onget="return this.mCurrentBrowser.contentTitle;"
+ readonly="true"/>
+
+ <property name="securityUI"
+ onget="return this.mCurrentBrowser.securityUI;"
+ readonly="true"/>
+
+ <property name="userTypedValue"
+ onget="return this.mCurrentBrowser.userTypedValue;"
+ onset="return this.mCurrentBrowser.userTypedValue = val;"/>
+
+ <property name="droppedLinkHandler"
+ onget="return this.mCurrentBrowser.droppedLinkHandler;"
+ onset="return this.mCurrentBrowser.droppedLinkHandler = val;"/>
+
+ <property name="showWindowResizer"
+ onget="return this.mCurrentBrowser.showWindowResizer;"
+ onset="return this.mCurrentBrowser.showWindowResizer = val;"/>
+
+ <property name="docShellIsActive"
+ onget="return this.mCurrentBrowser.docShellIsActive;"
+ onset="return this.mCurrentBrowser.docShellIsActive = val;"/>
+
+ <property name="fullZoom"
+ onget="return this.mCurrentBrowser.fullZoom;"
+ onset="return this.mCurrentBrowser.fullZoom = val;"/>
+
+ <property name="textZoom"
+ onget="return this.mCurrentBrowser.textZoom;"
+ onset="return this.mCurrentBrowser.textZoom = val;"/>
+
+ <property name="isSyntheticDocument"
+ onget="return this.mCurrentBrowser.isSyntheticDocument;"
+ readonly="true"/>
+
+ <property name="messageManager"
+ onget="return window.messageManager;"
+ readonly="true"/>
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body>
+ <![CDATA[
+ var maxUndoDepth = 0;
+ switch (aTopic) {
+ case "browser:purge-session-history":
+ break;
+
+ case "nsPref:changed":
+ if (aData == "browser.tabs.max_tabs_undo") {
+ maxUndoDepth = Math.max(0, Services.prefs.getIntPref(aData));
+ break;
+ }
+
+ default:
+ return;
+ }
+
+ // Wipe out savedBrowsers since history is gone
+ while (this.savedBrowsers.length > maxUndoDepth) {
+ var tabData = this.savedBrowsers.pop();
+ var deadBrowser = tabData.browserData.browser;
+ delete tabData.browserData;
+ this.removeBrowser(deadBrowser);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="_fastFind">null</field>
+ <property name="fastFind" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._fastFind) {
+ this._fastFind = Cc["@mozilla.org/typeaheadfind;1"]
+ .createInstance(Ci.nsITypeAheadFind);
+ this._fastFind.init(this.docShell);
+ }
+ return this._fastFind;
+ ]]>
+ </getter>
+ </property>
+
+ <field name="_lastSearchString">null</field>
+ <field name="_lastSearchHighlight">false</field>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "keypress":
+ this._handleKeyEvent(aEvent);
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <constructor>
+ <![CDATA[
+ document.addEventListener("keypress", this);
+ this.arrowKeysShouldWrap = AppConstants.platform == "macosx";
+ // Bail out early if we are in tabmail. See Bug 521803.
+ if (!this.mPanelContainer)
+ return;
+
+ this.mCurrentBrowser = this.mPanelContainer.firstChild.firstChild.firstChild;
+ this.mCurrentTab = this.tabContainer.firstChild;
+
+ var uniqueId = "panel" + this.nextTabNumber++;
+ this.mPanelContainer.childNodes[0].id = uniqueId;
+ this.tabs[0].linkedPanel = uniqueId;
+ this.tabs[0].linkedBrowser = this.mCurrentBrowser;
+
+ // Ensure the browser's session history and security UI are wired up
+ // note that toolkit browser automatically inits its security UI
+ // when you get it but for xpfe you need to init it explicitly
+ if (!this.mCurrentBrowser.securityUI)
+ this.mCurrentBrowser.init();
+
+ // Wire up the tab's progress listener and filter.
+ var tabListener = this.mTabProgressListener(this.mCurrentTab,
+ this.mCurrentBrowser,
+ false);
+ var filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Ci.nsIWebProgress);
+ filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
+ this.webProgress.addProgressListener(filter,
+ Ci.nsIWebProgress.NOTIFY_ALL);
+ this.mTabListeners[0] = tabListener;
+ this.mTabFilters[0] = filter;
+
+ if (!Services.prefs.getBoolPref("browser.tabs.autoHide") &&
+ !Services.prefs.getBoolPref("browser.tabs.forceHide") &&
+ window.toolbar.visible)
+ this.mStrip.collapsed = false;
+
+ var t = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "tab");
+ t.setAttribute("label", this.mStringBundle.getString("tabs.untitled"));
+ t.setAttribute("crop", "end");
+ t.className = "tabbrowser-tab";
+ t.style.maxWidth = Services.prefs.getIntPref("browser.tabs.tabMaxWidth") + "px";
+ t.style.minWidth = Services.prefs.getIntPref("browser.tabs.tabMinWidth") + "px";
+ t.width = 0;
+ t.flex = 100;
+ t.setAttribute("validate", "never");
+ t.setAttribute("onerror", "this.parentNode.parentNode.parentNode.parentNode.addToMissedIconCache(this.getAttribute('image')); this.removeAttribute('image');");
+ this.referenceTab = t;
+
+ Services.obs.addObserver(this, "browser:purge-session-history");
+ Services.prefs.addObserver("browser.tabs.max_tabs_undo", this);
+
+ var onclick = this.getAttribute("oncontentclick");
+ if (onclick)
+ this.onContentClick = new Function("event", onclick);
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ document.removeEventListener("keypress", this);
+ // Bail out early if we are in tabmail. See Bug 521803.
+ if (!this.mPanelContainer)
+ return;
+
+ for (var i = 0; i < this.mTabListeners.length; ++i) {
+ this.browsers[i].webProgress.removeProgressListener(this.mTabFilters[i]);
+ this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
+ this.mTabFilters[i] = null;
+ this.mTabListeners[i] = null;
+ }
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ Services.prefs.removeObserver("browser.tabs.max_tabs_undo", this);
+ this.savedBrowsers.forEach(function(aTabData) {
+ delete aTabData.browserData;
+ });
+ ]]>
+ </destructor>
+
+ <!-- Deprecated stuff, implemented for backwards compatibility. -->
+ <property name="mTabContainer" readonly="true"
+ onget="return this.tabContainer;"/>
+ <property name="mTabs" readonly="true"
+ onget="return this.tabs;"/>
+ </implementation>
+
+ <handlers>
+ <handler event="select" action="if (event.originalTarget == this.mPanelContainer) this.updateCurrentBrowser();"/>
+
+ <handler event="DOMLinkAdded" phase="capturing" action="this.onLinkEvent(event);"/>
+ <handler event="DOMLinkChanged" phase="capturing" action="this.onLinkEvent(event);"/>
+
+ <handler event="DOMWindowClose" phase="capturing">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ if (this.tabs.length == 1)
+ return;
+
+ this.removeTab(this._getTabForContentWindow(event.target));
+ event.preventDefault();
+ ]]>
+ </handler>
+
+ <handler event="DOMWebNotificationClicked" phase="capturing">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ // The user clicked a desktop notification; make sure its
+ // tab is brought to the front and then raise the window.
+ this.selectedTab = this._getTabForContentWindow(event.target.top);
+ window.focus();
+ ]]>
+ </handler>
+
+ <handler event="DOMWillOpenModalDialog" phase="capturing">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ // We're about to open a modal dialog, make sure the opening
+ // tab is brought to the front.
+ this.selectedTab = this._getTabForContentWindow(event.target.top);
+ ]]>
+ </handler>
+
+ <handler event="DOMTitleChanged">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ var contentWin = event.target.defaultView;
+ if (contentWin != contentWin.top)
+ return;
+
+ var tab = this._getTabForContentWindow(contentWin);
+ if (!tab)
+ return;
+
+ this.setTabTitle(tab);
+ if (tab == this.mCurrentTab)
+ this.updateTitlebar();
+ ]]>
+ </handler>
+
+ <handler event="click" phase="capturing" group="system">
+ <![CDATA[
+ if (this.onContentClick)
+ this.onContentClick(event);
+ ]]>
+ </handler>
+
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-arrowscrollbox"
+ extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
+ <implementation>
+ <!-- Override scrollbox.xml method, since our scrollbox's children are
+ inherited from the binding parent -->
+ <method name="_getScrollableElements">
+ <body>
+ <![CDATA[
+ return Array.from(document.getBindingParent(this).childNodes)
+ .filter(this._canScrollToElement,
+ this);
+ ]]>
+ </body>
+ </method>
+ <method name="_canScrollToElement">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ return !aTab.pinned && !aTab.hidden;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="underflow">
+ <![CDATA[
+ if (event.detail == 0)
+ return; // Ignore vertical events
+
+ var tabs = document.getBindingParent(this);
+ tabs.removeAttribute("overflow");
+ ]]>
+ </handler>
+ <handler event="overflow">
+ <![CDATA[
+ if (event.detail == 0)
+ return; // Ignore vertical events
+
+ var tabs = document.getBindingParent(this);
+ tabs.setAttribute("overflow", true);
+ tabs._handleTabSelect(false);
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-tabs"
+ extends="chrome://global/content/bindings/tabbox.xml#tabs">
+ <content>
+ <xul:stack flex="1" class="tabs-stack">
+ <xul:vbox>
+ <xul:spacer flex="1"/>
+ <xul:hbox class="tabs-bottom" align="center"/>
+ </xul:vbox>
+ <xul:vbox>
+ <xul:hbox>
+ <xul:stack>
+ <xul:spacer class="tabs-left"/>
+ <xul:toolbarbutton class="tabs-newbutton" context=""
+ anonid="tabstrip-newbutton"
+ xbl:inherits="oncommand=onnewtab,onclick=onnewtabclick,tooltiptext=tooltiptextnew"/>
+ </xul:stack>
+ <xul:arrowscrollbox anonid="arrowscrollbox"
+ class="tabbrowser-arrowscrollbox"
+ flex="1"
+ xbl:inherits="smoothscroll"
+ orient="horizontal"
+ style="min-width: 1px;">
+ <children includes="tab"/>
+ <xul:spacer class="tabs-right" flex="1"/>
+ </xul:arrowscrollbox>
+ <children/>
+ <xul:stack>
+ <xul:spacer class="tabs-right"/>
+ <xul:hbox class="tabs-closebutton-box" align="stretch" pack="end">
+ <xul:toolbarbutton class="tabs-alltabs-button" context=""
+ anonid="alltabs-button"
+ type="menu"
+ xbl:inherits="tooltiptext=tooltiptextalltabs">
+ <xul:menupopup class="tabs-alltabs-popup"
+ anonid="alltabs-popup"
+ position="after_end"/>
+ </xul:toolbarbutton>
+ <xul:hbox align="center">
+ <xul:toolbarbutton class="tabs-closebutton close-button" context=""
+ anonid="tabstrip-closebutton"
+ xbl:inherits="disabled=disableclose,oncommand=onclosetab,tooltiptext=tooltiptextclose"/>
+ </xul:hbox>
+ </xul:hbox>
+ </xul:stack>
+ </xul:hbox>
+ <xul:spacer class="tabs-bottom-spacer"/>
+ </xul:vbox>
+ </xul:stack>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor>
+ <![CDATA[
+ var tab = this.firstChild;
+ // set the tabstrip's minWidth too, otherwise it immediately overflows
+ this.arrowScrollbox.style.minWidth =
+ tab.style.minWidth = Services.prefs.getIntPref("browser.tabs.tabMinWidth") + "px";
+ tab.style.maxWidth = Services.prefs.getIntPref("browser.tabs.tabMaxWidth") + "px";
+ window.addEventListener("resize", this);
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ window.removeEventListener("resize", this);
+ ]]>
+ </destructor>
+
+ <field name="arrowScrollboxWidth">0</field>
+
+ <field name="arrowScrollbox">
+ document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
+ </field>
+
+ <method name="_handleTabSelect">
+ <parameter name="aSmoothScroll"/>
+ <body>
+ <![CDATA[
+ if (this.getAttribute("overflow") == "true")
+ this.arrowScrollbox.ensureElementIsVisible(this.selectedItem,
+ aSmoothScroll);
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ switch (aEvent.type)
+ {
+ case "resize":
+ if (aEvent.target != window)
+ break;
+ var width = this.arrowScrollbox.boxObject.width;
+ if (width != this.arrowScrollboxWidth)
+ {
+ this._handleTabSelect(false);
+ this.arrowScrollboxWidth = width;
+ }
+ break;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="mAllTabsPopup">
+ document.getAnonymousElementByAttribute(this, "anonid", "alltabs-popup");
+ </field>
+
+ <field name="_animateElement">
+ this.arrowScrollbox._scrollButtonDown;
+ </field>
+
+ <method name="_notifyBackgroundTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var scrollRect = this.arrowScrollbox.scrollClientRect;
+ var tab = aTab.getBoundingClientRect();
+
+ // Is the new tab already completely visible?
+ if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
+ return;
+
+ if (this.arrowScrollbox.smoothScroll) {
+ let selected = this.selectedItem.getBoundingClientRect();
+
+ /* Can we make both the new tab and the selected tab completely
+ visible? */
+ if (!selected ||
+ Math.max(tab.right - selected.left, selected.right - tab.left) <= scrollRect.width) {
+ this.arrowScrollbox.ensureElementIsVisible(aTab);
+ return;
+ }
+
+ this.arrowScrollbox.scrollByPixels(this.arrowScrollbox._isRTLScrollbox ?
+ selected.right - scrollRect.right :
+ selected.left - scrollRect.left);
+ }
+
+ if (!this._animateElement.hasAttribute("notifybgtab")) {
+ this._animateElement.setAttribute("notifybgtab", "true");
+ setTimeout(function(ele) {
+ ele.removeAttribute("notifybgtab");
+ }, 150, this._animateElement);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_handleNewTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (aTab.parentNode != this)
+ return;
+
+ if (aTab.getAttribute("selected") == "true") {
+ this._handleTabSelect();
+ } else {
+ this._notifyBackgroundTab(aTab);
+ }
+
+ /* XXXmano: this is a temporary workaround for bug 345399
+ * We need to manually update the scroll buttons disabled state
+ * if a tab was inserted to the overflow area or removed from it
+ * without any scrolling and when the tabbar has already
+ * overflowed.
+ */
+ this.arrowScrollbox._updateScrollButtonsDisabledState();
+ ]]>
+ </body>
+ </method>
+
+ <method name="_handleMouseScroll">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ // Javascript does not have a logical XOR operator.
+ if (aEvent.shiftKey != Services.prefs.getBoolPref("browser.tabs.mouseScrollAdvancesTab")) {
+ this.advanceSelectedTab(aEvent.detail < 0 ? -1 : 1);
+ aEvent.stopPropagation();
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="TabSelect" action="this._handleTabSelect();"/>
+
+ <handler event="transitionend">
+ <![CDATA[
+ if (event.propertyName == "max-width")
+ this._handleNewTab(event.target);
+ ]]>
+ </handler>
+
+ <handler event="DOMMouseScroll" phase="capturing">
+ <![CDATA[
+ this._handleMouseScroll(event);
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-alltabs-popup"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation implements="nsIDOMEventListener">
+ <method name="_tabOnTabClose">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ let menuItem = aEvent.target.mCorrespondingMenuitem;
+ if (menuItem)
+ menuItem.remove();
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ switch (aEvent.type)
+ {
+ case "TabClose":
+ this._tabOnTabClose(aEvent);
+ break;
+ case "TabOpen":
+ this._createTabMenuItem(aEvent.originalTarget);
+ break;
+ case "scroll":
+ this._updateTabsVisibilityStatus();
+ break;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_updateTabsVisibilityStatus">
+ <body>
+ <![CDATA[
+ let tabContainer = document.getBindingParent(this);
+ let tabstripBO = tabContainer.arrowScrollbox.scrollBoxObject;
+
+ for (let i = 0; i < this.childNodes.length; i++)
+ {
+ let curTabBO = this.childNodes[i].tab.boxObject;
+ if (curTabBO.screenX >= tabstripBO.screenX &&
+ curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
+ this.childNodes[i].removeAttribute("tabIsScrolled");
+ else
+ this.childNodes[i].setAttribute("tabIsScrolled", "true");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_createTabMenuItem">
+ <parameter name="aTabNode"/>
+ <body>
+ <![CDATA[
+ let menuItem = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "menuitem");
+ menuItem.setAttribute("class", "menuitem-iconic alltabs-item icon-holder");
+ menuItem.setAttribute("label", aTabNode.label);
+ menuItem.setAttribute("crop", aTabNode.getAttribute("crop"));
+
+ ["busy", "image", "selected"].forEach(
+ function(attribute)
+ {
+ if (aTabNode.hasAttribute(attribute))
+ {
+ menuItem.setAttribute(attribute, aTabNode.getAttribute(attribute));
+ }
+ }
+ );
+
+ // Keep some attributes of the menuitem in sync with its
+ // corresponding tab (e.g. the tab label)
+ aTabNode.mCorrespondingMenuitem = menuItem;
+ document.addBroadcastListenerFor(aTabNode, menuItem, "label");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "crop");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "image");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "busy");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "selected");
+ aTabNode.addEventListener("TabClose", this);
+ menuItem.tab = aTabNode;
+ this.appendChild(menuItem);
+ return menuItem;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="popupshowing">
+ <![CDATA[
+ // set up the menu popup
+ let tabcontainer = document.getBindingParent(this);
+ let tabs = tabcontainer.childNodes;
+
+ // Listen for changes in the tab bar.
+ let tabbrowser = document.getBindingParent(tabcontainer);
+ tabbrowser.addEventListener("TabOpen", this);
+ tabcontainer.arrowScrollbox.addEventListener("scroll", this);
+
+ for (let i = 0; i < tabs.length; i++)
+ this._createTabMenuItem(tabs[i]);
+ this._updateTabsVisibilityStatus();
+ ]]>
+ </handler>
+
+ <handler event="popuphiding">
+ <![CDATA[
+ // clear out the menu popup and remove the listeners
+ while (this.hasChildNodes())
+ {
+ let menuItem = this.lastChild;
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "label");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "crop");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "image");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "busy");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "selected");
+ menuItem.tab.removeEventListener("TabClose", this);
+ menuItem.tab.mCorrespondingMenuitem = null;
+ menuItem.tab = null;
+ menuItem.remove();
+ }
+ let tabcontainer = document.getBindingParent(this);
+ tabcontainer.arrowScrollbox.removeEventListener("scroll", this);
+ document.getBindingParent(tabcontainer).removeEventListener("TabOpen", this);
+ ]]>
+ </handler>
+
+ <handler event="command">
+ <![CDATA[
+ let tabcontainer = document.getBindingParent(this);
+ let tab = event.target.tab;
+ if (tabcontainer.selectedItem == tab)
+ tabcontainer._handleTabSelect();
+ else
+ tabcontainer.selectedItem = tab;
+ ]]>
+ </handler>
+
+ <handler event="DOMMenuItemActive">
+ <![CDATA[
+ var tab = event.target.tab;
+ if (tab) {
+ let overLink = tab.linkedBrowser.currentURI.spec;
+ if (overLink == "about:blank")
+ overLink = "";
+ XULBrowserWindow.setOverLink(overLink, null);
+ }
+ ]]></handler>
+
+ <handler event="DOMMenuItemInactive">
+ <![CDATA[
+ XULBrowserWindow.setOverLink("", null);
+ ]]></handler>
+
+ </handlers>
+ </binding>
+</bindings>
diff --git a/comm/suite/browser/test/browser/alltabslistener.html b/comm/suite/browser/test/browser/alltabslistener.html
new file mode 100644
index 0000000000..166c31037a
--- /dev/null
+++ b/comm/suite/browser/test/browser/alltabslistener.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<title>Test page for bug 463387</title>
+</head>
+<body>
+<p>Test page for bug 463387</p>
+</body>
+</html>
diff --git a/comm/suite/browser/test/browser/authenticate.sjs b/comm/suite/browser/test/browser/authenticate.sjs
new file mode 100644
index 0000000000..2f8c85adc1
--- /dev/null
+++ b/comm/suite/browser/test/browser/authenticate.sjs
@@ -0,0 +1,210 @@
+function handleRequest(request, response)
+{
+ try {
+ reallyHandleRequest(request, response);
+ } catch (e) {
+ response.setStatusLine("1.0", 200, "AlmostOK");
+ response.write("Error handling request: " + e);
+ }
+}
+
+
+function reallyHandleRequest(request, response) {
+ var match;
+ var requestAuth = true, requestProxyAuth = true;
+
+ // Allow the caller to drive how authentication is processed via the query.
+ // Eg, http://localhost:8888/authenticate.sjs?user=foo&realm=bar
+ // The extra ? allows the user/pass/realm checks to succeed if the name is
+ // at the beginning of the query string.
+ var query = "?" + request.queryString;
+
+ var expected_user = "", expected_pass = "", realm = "mochitest";
+ var proxy_expected_user = "", proxy_expected_pass = "", proxy_realm = "mochi-proxy";
+ var huge = false, anonymous = false;
+ var authHeaderCount = 1;
+ // user=xxx
+ match = /[^_]user=([^&]*)/.exec(query);
+ if (match)
+ expected_user = match[1];
+
+ // pass=xxx
+ match = /[^_]pass=([^&]*)/.exec(query);
+ if (match)
+ expected_pass = match[1];
+
+ // realm=xxx
+ match = /[^_]realm=([^&]*)/.exec(query);
+ if (match)
+ realm = match[1];
+
+ // proxy_user=xxx
+ match = /proxy_user=([^&]*)/.exec(query);
+ if (match)
+ proxy_expected_user = match[1];
+
+ // proxy_pass=xxx
+ match = /proxy_pass=([^&]*)/.exec(query);
+ if (match)
+ proxy_expected_pass = match[1];
+
+ // proxy_realm=xxx
+ match = /proxy_realm=([^&]*)/.exec(query);
+ if (match)
+ proxy_realm = match[1];
+
+ // huge=1
+ match = /huge=1/.exec(query);
+ if (match)
+ huge = true;
+
+ // multiple=1
+ match = /multiple=([^&]*)/.exec(query);
+ if (match)
+ authHeaderCount = match[1]+0;
+
+ // anonymous=1
+ match = /anonymous=1/.exec(query);
+ if (match)
+ anonymous = true;
+
+ // Look for an authentication header, if any, in the request.
+ //
+ // EG: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
+ //
+ // This test only supports Basic auth. The value sent by the client is
+ // "username:password", obscured with base64 encoding.
+
+ var actual_user = "", actual_pass = "", authHeader, authPresent = false;
+ if (request.hasHeader("Authorization")) {
+ authPresent = true;
+ authHeader = request.getHeader("Authorization");
+ match = /Basic (.+)/.exec(authHeader);
+ if (match.length != 2)
+ throw "Couldn't parse auth header: " + authHeader;
+
+ var userpass = base64ToString(match[1]); // no atob() :-(
+ match = /(.*):(.*)/.exec(userpass);
+ if (match.length != 3)
+ throw "Couldn't decode auth header: " + userpass;
+ actual_user = match[1];
+ actual_pass = match[2];
+ }
+
+ var proxy_actual_user = "", proxy_actual_pass = "";
+ if (request.hasHeader("Proxy-Authorization")) {
+ authHeader = request.getHeader("Proxy-Authorization");
+ match = /Basic (.+)/.exec(authHeader);
+ if (match.length != 2)
+ throw "Couldn't parse auth header: " + authHeader;
+
+ var userpass = base64ToString(match[1]); // no atob() :-(
+ match = /(.*):(.*)/.exec(userpass);
+ if (match.length != 3)
+ throw "Couldn't decode auth header: " + userpass;
+ proxy_actual_user = match[1];
+ proxy_actual_pass = match[2];
+ }
+
+ // Don't request authentication if the credentials we got were what we
+ // expected.
+ if (expected_user == actual_user &&
+ expected_pass == actual_pass) {
+ requestAuth = false;
+ }
+ if (proxy_expected_user == proxy_actual_user &&
+ proxy_expected_pass == proxy_actual_pass) {
+ requestProxyAuth = false;
+ }
+
+ if (anonymous) {
+ if (authPresent) {
+ response.setStatusLine("1.0", 400, "Unexpected authorization header found");
+ } else {
+ response.setStatusLine("1.0", 200, "Authorization header not found");
+ }
+ } else {
+ if (requestProxyAuth) {
+ response.setStatusLine("1.0", 407, "Proxy authentication required");
+ for (i = 0; i < authHeaderCount; ++i)
+ response.setHeader("Proxy-Authenticate", "basic realm=\"" + proxy_realm + "\"", true);
+ } else if (requestAuth) {
+ response.setStatusLine("1.0", 401, "Authentication required");
+ for (i = 0; i < authHeaderCount; ++i)
+ response.setHeader("WWW-Authenticate", "basic realm=\"" + realm + "\"", true);
+ } else {
+ response.setStatusLine("1.0", 200, "OK");
+ }
+ }
+
+ response.setHeader("Content-Type", "application/xhtml+xml", false);
+ response.write("<html xmlns='http://www.w3.org/1999/xhtml'>");
+ response.write("<p>Login: <span id='ok'>" + (requestAuth ? "FAIL" : "PASS") + "</span></p>\n");
+ response.write("<p>Proxy: <span id='proxy'>" + (requestProxyAuth ? "FAIL" : "PASS") + "</span></p>\n");
+ response.write("<p>Auth: <span id='auth'>" + authHeader + "</span></p>\n");
+ response.write("<p>User: <span id='user'>" + actual_user + "</span></p>\n");
+ response.write("<p>Pass: <span id='pass'>" + actual_pass + "</span></p>\n");
+
+ if (huge) {
+ response.write("<div style='display: none'>");
+ for (i = 0; i < 100000; i++) {
+ response.write("123456789\n");
+ }
+ response.write("</div>");
+ response.write("<span id='footnote'>This is a footnote after the huge content fill</span>");
+ }
+
+ response.write("</html>");
+}
+
+
+// base64 decoder
+//
+// Yoinked from extensions/xml-rpc/src/nsXmlRpcClient.js because btoa()
+// doesn't seem to exist. :-(
+/* Convert Base64 data to a string */
+const toBinaryTable = [
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+ 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
+ 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
+ -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+ 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
+];
+const base64Pad = '=';
+
+function base64ToString(data) {
+
+ var result = '';
+ var leftbits = 0; // number of bits decoded, but yet to be appended
+ var leftdata = 0; // bits decoded, but yet to be appended
+
+ // Convert one by one.
+ for (var i = 0; i < data.length; i++) {
+ var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
+ var padding = (data[i] == base64Pad);
+ // Skip illegal characters and whitespace
+ if (c == -1) continue;
+
+ // Collect data into leftdata, update bitcount
+ leftdata = (leftdata << 6) | c;
+ leftbits += 6;
+
+ // If we have 8 or more bits, append 8 bits to the result
+ if (leftbits >= 8) {
+ leftbits -= 8;
+ // Append if not padding.
+ if (!padding)
+ result += String.fromCharCode((leftdata >> leftbits) & 0xff);
+ leftdata &= (1 << leftbits) - 1;
+ }
+ }
+
+ // If there are any bits left, the base64 string was corrupted
+ if (leftbits)
+ throw Components.Exception('Corrupted base64 string');
+
+ return result;
+}
diff --git a/comm/suite/browser/test/browser/browser.ini b/comm/suite/browser/test/browser/browser.ini
new file mode 100644
index 0000000000..a6b00157c6
--- /dev/null
+++ b/comm/suite/browser/test/browser/browser.ini
@@ -0,0 +1,38 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_alltabslistener.js]
+support-files = alltabslistener.html
+[browser_bug329212.js]
+support-files = title_test.svg
+[browser_bug409624.js]
+[browser_bug413915.js]
+[browser_bug427559.js]
+[browser_bug435325.js]
+[browser_bug462289.js]
+skip-if = toolkit == "cocoa"
+[browser_bug519216.js]
+[browser_bug561636.js]
+[browser_bug562649.js]
+[browser_bug581947.js]
+[browser_bug585511.js]
+[browser_bug595507.js]
+[browser_bug623155.js]
+support-files = redirect_bug623155.sjs
+[browser_fayt.js]
+[browser_page_style_menu.js]
+support-files = page_style_sample.html
+[browser_notification_tab_switching.js]
+support-files = file_dom_notifications.html
+[browser_pageInfo.js]
+support-files = feed_tab.html
+[browser_popupNotification.js]
+[browser_privatebrowsing_protocolhandler.js]
+support-files = browser_privatebrowsing_protocolhandler_page.html
+[browser_relatedTabs.js]
+[browser_scope.js]
+[browser_selectTabAtIndex.js]
+[browser_urlbarCopying.js]
+support-files =
+ authenticate.sjs
diff --git a/comm/suite/browser/test/browser/browser_alltabslistener.js b/comm/suite/browser/test/browser/browser_alltabslistener.js
new file mode 100644
index 0000000000..609a4e66e8
--- /dev/null
+++ b/comm/suite/browser/test/browser/browser_alltabslistener.js
@@ -0,0 +1,201 @@
+const gCompleteState = Ci.nsIWebProgressListener.STATE_STOP +
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+
+var gFrontProgressListener = {
+ onProgressChange: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ },
+
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
+ var state = "onStateChange";
+ info("FrontProgress: " + state + " 0x" + aStateFlags.toString(16));
+ ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
+ is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
+ gFrontNotificationsPos++;
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) {
+ var state = "onLocationChange";
+ info("FrontProgress: " + state + " " + aLocationURI.spec);
+ ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
+ is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
+ gFrontNotificationsPos++;
+ },
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
+ },
+
+ onSecurityChange: function (aWebProgress, aRequest, aState) {
+ var state = "onSecurityChange";
+ info("FrontProgress: " + state + " 0x" + aState.toString(16));
+ ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
+ is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
+ gFrontNotificationsPos++;
+ }
+}
+
+var gAllProgressListener = {
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ var state = "onStateChange";
+ info("AllProgress: " + state + " 0x" + aStateFlags.toString(16));
+ is(aBrowser, gTestBrowser, state + " notification came from the correct browser");
+ ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
+ is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
+ gAllNotificationsPos++;
+
+ if ((aStateFlags & gCompleteState) == gCompleteState) {
+ is(gAllNotificationsPos, gAllNotifications.length, "Saw the expected number of notifications");
+ is(gFrontNotificationsPos, gFrontNotifications.length, "Saw the expected number of frontnotifications");
+ executeSoon(gNextTest);
+ }
+ },
+
+ onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) {
+ var state = "onLocationChange";
+ info("AllProgress: " + state + " " + aLocationURI.spec);
+ is(aBrowser, gTestBrowser, state + " notification came from the correct browser");
+ ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
+ is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
+ gAllNotificationsPos++;
+ },
+
+ onStatusChange: function (aBrowser, aWebProgress, aRequest, aStatus, aMessage) {
+ var state = "onStatusChange";
+ is(aBrowser, gTestBrowser, state + " notification came from the correct browser");
+ },
+
+ onSecurityChange: function (aBrowser, aWebProgress, aRequest, aState) {
+ var state = "onSecurityChange";
+ info("AllProgress: " + state + " 0x" + aState.toString(16));
+ is(aBrowser, gTestBrowser, state + " notification came from the correct browser");
+ ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
+ is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
+ gAllNotificationsPos++;
+ }
+}
+
+var gFrontNotifications, gAllNotifications, gFrontNotificationsPos, gAllNotificationsPos;
+var gBackgroundTab, gForegroundTab, gBackgroundBrowser, gForegroundBrowser, gTestBrowser;
+var gTestPage = "/browser/suite/browser/test/browser/alltabslistener.html";
+var gNextTest;
+
+function test() {
+ waitForExplicitFinish();
+
+ gBackgroundTab = getBrowser().addTab("about:blank");
+ gForegroundTab = getBrowser().addTab("about:blank");
+ gBackgroundBrowser = getBrowser().getBrowserForTab(gBackgroundTab);
+ gForegroundBrowser = getBrowser().getBrowserForTab(gForegroundTab);
+ getBrowser().selectedTab = gForegroundTab;
+
+ // We must wait until the about:blank page has completed loading before
+ // starting tests or we get notifications from that
+ gForegroundBrowser.addEventListener("load", startTests, true);
+}
+
+function runTest(browser, url, next) {
+ gFrontNotificationsPos = 0;
+ gAllNotificationsPos = 0;
+ gNextTest = next;
+ gTestBrowser = browser;
+ browser.loadURI(url);
+}
+
+function startTests() {
+ gForegroundBrowser.removeEventListener("load", startTests, true);
+ executeSoon(startTest1);
+}
+
+function startTest1() {
+ info("\nTest 1");
+ getBrowser().addProgressListener(gFrontProgressListener);
+ getBrowser().addTabsProgressListener(gAllProgressListener);
+
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = gAllNotifications;
+ runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest2);
+}
+
+function startTest2() {
+ info("\nTest 2");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = gAllNotifications;
+ runTest(gForegroundBrowser, "https://example.com" + gTestPage, startTest3);
+}
+
+function startTest3() {
+ info("\nTest 3");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = [];
+ runTest(gBackgroundBrowser, "http://example.org" + gTestPage, startTest4);
+}
+
+function startTest4() {
+ info("\nTest 4");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = [];
+ runTest(gBackgroundBrowser, "https://example.com" + gTestPage, startTest5);
+}
+
+function startTest5() {
+ info("\nTest 5");
+ // Switch the foreground browser
+ [gForegroundBrowser, gBackgroundBrowser] = [gBackgroundBrowser, gForegroundBrowser];
+ [gForegroundTab, gBackgroundTab] = [gBackgroundTab, gForegroundTab];
+ // Avoid the onLocationChange this will fire
+ getBrowser().removeProgressListener(gFrontProgressListener);
+ getBrowser().selectedTab = gForegroundTab;
+ getBrowser().addProgressListener(gFrontProgressListener);
+
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = gAllNotifications;
+ runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest6);
+}
+
+function startTest6() {
+ info("\nTest 6");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = [];
+ runTest(gBackgroundBrowser, "http://example.org" + gTestPage, finishTest);
+}
+
+function finishTest() {
+ getBrowser().removeProgressListener(gFrontProgressListener);
+ getBrowser().removeTabsProgressListener(gAllProgressListener);
+ getBrowser().removeTab(gBackgroundTab);
+ getBrowser().removeTab(gForegroundTab);
+ finish();
+}
diff --git a/comm/suite/browser/test/browser/browser_bug329212.js b/comm/suite/browser/test/browser/browser_bug329212.js
new file mode 100644
index 0000000000..86925a4cb8
--- /dev/null
+++ b/comm/suite/browser/test/browser/browser_bug329212.js
@@ -0,0 +1,42 @@
+function test() {
+ waitForExplicitFinish();
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function loadListener() {
+ gBrowser.selectedBrowser.removeEventListener("load", loadListener, true);
+
+ let doc = gBrowser.contentDocument;
+ let tooltip = document.getElementById("aHTMLTooltip");
+
+ ok(FillInHTMLTooltip(doc.getElementById("svg1")), "should get title");
+ is(tooltip.getAttribute("label"), "This is a non-root SVG element title");
+
+ ok(FillInHTMLTooltip(doc.getElementById("text1")), "should get title");
+ is(tooltip.getAttribute("label"), "\n\n\n This is a title\n\n ");
+
+ ok(!FillInHTMLTooltip(doc.getElementById("text2")), "should not get title");
+
+ ok(!FillInHTMLTooltip(doc.getElementById("text3")), "should not get title");
+
+ ok(FillInHTMLTooltip(doc.getElementById("link1")), "should get title");
+ is(tooltip.getAttribute("label"), "\n This is a title\n ");
+ ok(FillInHTMLTooltip(doc.getElementById("text4")), "should get title");
+ is(tooltip.getAttribute("label"), "\n This is a title\n ");
+
+ ok(!FillInHTMLTooltip(doc.getElementById("link2")), "should not get title");
+
+ ok(FillInHTMLTooltip(doc.getElementById("link3")), "should get title");
+ isnot(tooltip.getAttribute("label"), "");
+
+ ok(FillInHTMLTooltip(doc.getElementById("link4")), "should get title");
+ is(tooltip.getAttribute("label"), "This is an xlink:title attribute");
+
+ ok(!FillInHTMLTooltip(doc.getElementById("text5")), "should not get title");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ }, true);
+
+ content.location =
+ "http://mochi.test:8888/browser/suite/browser/test/browser/title_test.svg";
+}
+
diff --git a/comm/suite/browser/test/browser/browser_bug409624.js b/comm/suite/browser/test/browser/browser_bug409624.js
new file mode 100644
index 0000000000..c3a800dffa
--- /dev/null
+++ b/comm/suite/browser/test/browser/browser_bug409624.js
@@ -0,0 +1,57 @@
+/* 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, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ // This test relies on the form history being empty to start with delete
+ // all the items first.
+ FormHistory.update({ op: "remove" },
+ { handleError: function (error) {
+ do_throw("Error occurred updating form history: " + error);
+ },
+ handleCompletion: function (reason) { if (!reason) test2(); },
+ });
+}
+
+function test2()
+{
+ let prefService = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+
+ let findBar = document.getElementById("FindToolbar");
+ let textbox = findBar.getElement("findbar-textbox");
+
+ let temp = {};
+ ChromeUtils.import("resource:///modules/Sanitizer.jsm", temp);
+ let s = temp.Sanitizer;
+ let prefBranch = prefService.getBranch("privacy.clearOnShutdown.");
+
+ prefBranch.setBoolPref("cache", false);
+ prefBranch.setBoolPref("cookies", false);
+ prefBranch.setBoolPref("downloads", false);
+ prefBranch.setBoolPref("formdata", true);
+ prefBranch.setBoolPref("history", false);
+ prefBranch.setBoolPref("offlineApps", false);
+ prefBranch.setBoolPref("passwords", false);
+ prefBranch.setBoolPref("sessions", false);
+ prefBranch.setBoolPref("siteSettings", false);
+
+ prefService.setBoolPref("privacy.sanitize.promptOnSanitize", false);
+
+ // Sanitize now so we can test the baseline point.
+ s.sanitize();
+ ok(!gFindBar.hasTransactions, "pre-test baseline for sanitizer");
+
+ gFindBar.getElement("findbar-textbox").value = "m";
+ ok(gFindBar.hasTransactions, "formdata can be cleared after input");
+
+ s.sanitize();
+ is(gFindBar.getElement("findbar-textbox").value, "", "findBar textbox should be empty after sanitize");
+ ok(!gFindBar.hasTransactions, "No transactions after sanitize");
+
+ finish();
+}
diff --git a/comm/suite/browser/test/browser/browser_bug413915.js b/comm/suite/browser/test/browser/browser_bug413915.js
new file mode 100644
index 0000000000..98b968a1b3
--- /dev/null
+++ b/comm/suite/browser/test/browser/browser_bug413915.js
@@ -0,0 +1,70 @@
+ChromeUtils.defineModuleGetter(this, "Feeds",
+ "resource:///modules/Feeds.jsm");
+
+function test() {
+ var exampleUri = Services.io.newURI("http://example.com/");
+ var secman = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var principal = secman.createCodebasePrincipal(exampleUri, {});
+
+ function testIsFeed(aTitle, aHref, aType, aKnown) {
+ var link = {
+ title: aTitle,
+ href: aHref,
+ type: aType,
+ ownerDocument: {
+ characterSet: "UTF-8"
+ }
+ };
+ return Feeds.isValidFeed(link, principal, aKnown);
+ }
+
+ var href = "http://example.com/feed/";
+ var atomType = "application/atom+xml";
+ var funkyAtomType = " aPPLICAtion/Atom+XML ";
+ var rssType = "application/rss+xml";
+ var funkyRssType = " Application/RSS+XML ";
+ var rdfType = "application/rdf+xml";
+ var texmlType = "text/xml";
+ var appxmlType = "application/xml";
+ var noRss = "Foo";
+ var rss = "RSS";
+
+ // things that should be valid
+ ok(testIsFeed(noRss, href, atomType, false) == atomType,
+ "detect Atom feed");
+ ok(testIsFeed(noRss, href, funkyAtomType, false) == atomType,
+ "clean up and detect Atom feed");
+ ok(testIsFeed(noRss, href, rssType, false) == rssType,
+ "detect RSS feed");
+ ok(testIsFeed(noRss, href, funkyRssType, false) == rssType,
+ "clean up and detect RSS feed");
+
+ // things that should not be feeds
+ ok(testIsFeed(noRss, href, rdfType, false) == null,
+ "should not detect RDF non-feed");
+ ok(testIsFeed(rss, href, rdfType, false) == null,
+ "should not detect RDF feed from type and title");
+ ok(testIsFeed(noRss, href, texmlType, false) == null,
+ "should not detect text/xml non-feed");
+ ok(testIsFeed(rss, href, texmlType, false) == null,
+ "should not detect text/xml feed from type and title");
+ ok(testIsFeed(noRss, href, appxmlType, false) == null,
+ "should not detect application/xml non-feed");
+ ok(testIsFeed(rss, href, appxmlType, false) == null,
+ "should not detect application/xml feed from type and title");
+
+ // security check only, returns cleaned up type or "application/rss+xml"
+ ok(testIsFeed(noRss, href, atomType, true) == atomType,
+ "feed security check should return Atom type");
+ ok(testIsFeed(noRss, href, funkyAtomType, true) == atomType,
+ "feed security check should return cleaned up Atom type");
+ ok(testIsFeed(noRss, href, rssType, true) == rssType,
+ "feed security check should return RSS type");
+ ok(testIsFeed(noRss, href, funkyRssType, true) == rssType,
+ "feed security check should return cleaned up RSS type");
+ ok(testIsFeed(noRss, href, "", true) == rssType,
+ "feed security check without type should return RSS type");
+ ok(testIsFeed(noRss, href, "garbage", true) == "garbage",
+ "feed security check with garbage type should return garbage");
+}
diff --git a/comm/suite/browser/test/browser/browser_bug427559.js b/comm/suite/browser/test/browser/browser_bug427559.js
new file mode 100644
index 0000000000..bd34d89f5c
--- /dev/null
+++ b/comm/suite/browser/test/browser/browser_bug427559.js
@@ -0,0 +1,39 @@
+/* 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/. */
+
+/*
+ * Test bug 427559 to make sure focused elements that are no longer on the page
+ * will have focus transferred to the window when changing tabs back to that
+ * tab with the now-gone element.
+ */
+
+// Default focus on a button and have it kill itself on blur
+var testPage = 'data:text/html,<body><button onblur="this.remove();"><script>document.body.firstChild.focus();</script></body>';
+
+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,<html><body><iframe name='t'></iframe><form target='t' action='data:text/html,'><input><input id='s' type='submit'></form></body></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,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input required id='i'><input id='s' type='submit'></form>";
+ 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,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input><input id='i' required><input required><input id='s' type='submit'></form>";
+ 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,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+ 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,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input type='email' id='i' required><input id='s' type='submit'></form>";
+ 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,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+ 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,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+ 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,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+ 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,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+ 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,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input x-moz-errormessage='foo' required id='i'><input id='s' type='submit'></form>";
+ 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,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input type='email' required id='i'><input id='s' type='submit'></form>";
+ 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,<!DOCTYPE html><html><body><form id='content'></form></body></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,<iframe src=\"data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input required id='i'><input id='s' type='submit'></form>\"</iframe>";
+ 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 <https://www.bank1.com/#BG>, which displayes a cert
+ error page.
+
+3. Switch the tab to foreground.
+
+4. Check the URLbar's value, expecting <https://www.bank1.com/#BG>
+
+5. Load
+https://example.com/browser/browser/base/content/test/redirect_bug623155.sjs#FG
+ in the foreground tab.
+
+6. The redirected URI is <https://www.bank1.com/#FG>. And this is also
+ a cert-error page.
+
+7. Check the URLbar's value, expecting <https://www.bank1.com/#FG>
+
+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,<p>this is some dummy text</p>");
+ var tab2 = gBrowser.addTab("data:text/html;charset=utf-8,<p>this is some random text</p>");
+
+ 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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <title>Protocol registrar page</title>
+ </head>
+ <body>
+ <script>
+ navigator.registerProtocolHandler("testprotocol",
+ "https://example.com/foobar?uri=%s",
+ "Test Protocol");
+ </script>
+ </body>
+</html>
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: "<e>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: "<example.co>m",
+ copyExpected: "example.co"
+ },
+ {
+ copyVal: "e<x>ample.com",
+ copyExpected: "x"
+ },
+ {
+ copyVal: "<e>xample.com",
+ copyExpected: "e"
+ },
+
+ {
+ loadURL: "http://example.com/foo",
+ expectedURL: "example.com/foo",
+ copyExpected: "http://example.com/foo"
+ },
+ {
+ copyVal: "<example.com>/foo",
+ copyExpected: "http://example.com"
+ },
+ {
+ copyVal: "<example>.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: "<example.com/(>)()\xe9",
+ copyExpected: "http://example.com/("
+ },
+ {
+ copyVal: "e<xample.com/(>)()\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<xample.com/\xe9>\xe9",
+ copyExpected: "xample.com/\xe9"
+ },
+ {
+ copyVal: "<example.com/\xe9>\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<xample.com/?\xf7>\xf7",
+ copyExpected: "xample.com/?\xf7"
+ },
+ {
+ copyVal: "<example.com/?\xf7>\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: "<javascript:(>'%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: "<data:text/html,(>%C3%A9 %25P)",
+ copyExpected: "data:text/html,("
+ },
+ {
+ copyVal: "<data:text/html,(%C3%A9 %25P>)",
+ 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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=458579
+-->
+ <head>
+ <title>Test for page info feeds tab</title>
+
+ <!-- Straight up standard -->
+ <link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" />
+ <link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" />
+ <link rel="feed" title="3" href="/3.xml" />
+
+ </head>
+ <body>
+ </body>
+</html>
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 @@
+<html>
+<head>
+<script>
+"use strict";
+
+function showNotification1() {
+ var options = {
+ dir: undefined,
+ lang: undefined,
+ body: "Test body",
+ tag: "Test tag",
+ icon: undefined,
+ };
+ var n = new Notification("Test title", options);
+ n.addEventListener("click", function(event) {
+ event.preventDefault();
+ dump("Should focus new window.");
+ newWindow.focus();
+ });
+ return n;
+}
+
+function showNotification2() {
+ var options = {
+ dir: undefined,
+ lang: undefined,
+ body: "Test body",
+ tag: "Test tag",
+ icon: undefined,
+ };
+ return new Notification("Test title", options);
+}
+</script>
+</head>
+<body>
+<form id="notificationForm" onsubmit="showNotification();">
+ <input type="submit" value="Show notification" id="submit"/>
+</form>
+</body>
+</html>
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 @@
+<html>
+ <head>
+ <title>Test for page style menu</title>
+ <!-- data-state values:
+ 0: should not appear in the page style menu
+ 0-todo: should not appear in the page style menu, but does
+ 1: should appear in the page style menu
+ 2: should appear in the page style menu as the selected stylesheet -->
+ <link data-state="1" href="404.css" title="1" rel="alternate stylesheet">
+ <link data-state="0" title="2" rel="alternate stylesheet">
+ <link data-state="0" href="404.css" rel="alternate stylesheet">
+ <link data-state="0" href="404.css" title="" rel="alternate stylesheet">
+ <link data-state="1" href="404.css" title="3" rel="stylesheet alternate">
+ <link data-state="1" href="404.css" title="4" rel=" alternate stylesheet ">
+ <link data-state="1" href="404.css" title="5" rel="alternate stylesheet">
+ <link data-state="2" href="404.css" title="6" rel="stylesheet">
+ <link data-state="1" href="404.css" title="7" rel="foo stylesheet">
+ <link data-state="0" href="404.css" title="8" rel="alternate">
+ <link data-state="1" href="404.css" title="9" rel="alternate STYLEsheet">
+ <link data-state="1" href="404.css" title="10" rel="alternate stylesheet" media="">
+ <link data-state="1" href="404.css" title="11" rel="alternate stylesheet" media="all">
+ <link data-state="1" href="404.css" title="12" rel="alternate stylesheet" media="ALL ">
+ <link data-state="1" href="404.css" title="13" rel="alternate stylesheet" media="screen">
+ <link data-state="1" href="404.css" title="14" rel="alternate stylesheet" media=" Screen">
+ <link data-state="1" href="404.css" title="15" rel="alternate stylesheet" media="screen foo">
+ <link data-state="1" href="404.css" title="16" rel="alternate stylesheet" media="all screen">
+ <link data-state="0-todo" href="404.css" title="17" rel="alternate stylesheet" media="allscreen">
+ <link data-state="0-todo" href="404.css" title="18" rel="alternate stylesheet" media="_all">
+ </head>
+ <body></body>
+</html>
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 @@
+<svg width="640px" height="480px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0">
+ <title>This is a root SVG element's title</title>
+ <foreignObject>
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <svg xmlns="http://www.w3.org/2000/svg" id="svg1">
+ <title>This is a non-root SVG element title</title>
+ </svg>
+ </body>
+ </html>
+ </foreignObject>
+ <text id="text1" x="10px" y="32px" font-size="24px">
+ This contains only &lt;title&gt;
+ <title>
+
+
+ This is a title
+
+ </title>
+ </text>
+ <text id="text2" x="10px" y="96px" font-size="24px">
+ This contains only &lt;desc&gt;
+ <desc>This is a desc</desc>
+ </text>
+ <text id="text3" x="10px" y="128px" font-size="24px">
+ This contains nothing.
+ </text>
+ <a id="link1" xlink:href="#">
+ This link contains &lt;title&gt;
+ <title>
+ This is a title
+ </title>
+ <text id="text4" x="10px" y="192px" font-size="24px">
+ </text>
+ </a>
+ <a id="link2" xlink:href="#">
+ <text x="10px" y="192px" font-size="24px">
+ This text contains &lt;title&gt;
+ <title>
+ This is a title
+ </title>
+ </text>
+ </a>
+ <a id="link3" xlink:href="#" xlink:title="This is an xlink:title attribute">
+ <text x="10px" y="224px" font-size="24px">
+ This link contains &lt;title&gt; &amp; xlink:title attr.
+ <title>This is a title</title>
+ </text>
+ </a>
+ <a id="link4" xlink:href="#" xlink:title="This is an xlink:title attribute">
+ <text x="10px" y="256px" font-size="24px">
+ This link contains xlink:title attr.
+ </text>
+ </a>
+ <text id="text5" x="10px" y="160px" font-size="24px"
+ xlink:title="This is an xlink:title attribute but it isn't on a link" >
+ This contains nothing.
+ </text>
+</svg>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=739040
+-->
+<head>
+ <title>Test that we only sniff 512 bytes</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=739040">Mozilla Bug 739040</a>
+<p id="display">
+ <iframe id="validTestFrame" src="http://mochi.test:8888/tests/suite/browser/test/mochitest/valid-feed.xml"></iframe>
+ <iframe id="unsniffableTestFrame" src="http://mochi.test:8888/tests/suite/browser/test/mochitest/valid-unsniffable-feed.xml"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody">
+
+/** Test for Bug 739040 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ is($("validTestFrame").contentDocument.documentElement.id, "feedHandler",
+ "valid feed should be sniffed");
+ isnot($("unsniffableTestFrame").contentDocument.documentElement.id, "feedHandler",
+ "unsniffable feed should not be sniffed");
+});
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
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
--- /dev/null
+++ b/comm/suite/browser/test/mochitest/audio.ogg
Binary files 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 @@
+<rss version="2.0">
+ <channel>
+ <title>t</title>
+ </channel>
+</rss>
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 @@
+<rss version="2.0">
+ <channel>
+ <link>http://example.org/</link>
+ <title>t</title>
+ </channel>
+</rss>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:base="http://www.example.com/">
+
+ <title type="xhtml" xml:base="/foo/bar/">
+ <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>
+ </title>
+
+ <subtitle type="html" xml:base="/foo/bar/">
+ <![CDATA[
+ With a <em>special</em> subtitle (<img height="20px" src="baz.png" alt="base test sprite"/>)
+ ]]>
+ </subtitle>
+
+ <link href="http://example.org/"/>
+
+ <updated>2010-09-02T18:30:02Z</updated>
+
+ <author>
+ <name>John Doe</name>
+ </author>
+
+ <id>urn:uuid:22906062-ecbd-46e2-b6a7-3039506a398f</id>
+
+ <entry>
+ <title type="xhtml" xml:base="/foo/bar/">
+ <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>
+ </title>
+ <id>urn:uuid:b48083a7-71a7-4c9c-8515-b7c0d22955e7</id>
+ <updated>2010-09-02T18:30:02Z</updated>
+ <summary>Some text.</summary>
+ </entry>
+
+ <entry>
+ <title type="html" xml:base="/foo/bar/">
+ <![CDATA[
+ Some <abbr title="Hyper-text Mark-up Language">HTML</abbr> examples (<img height="20px" src="baz.png" alt="base test sprite"/>)
+ ]]>
+ </title>
+ <id>urn:uuid:1424967a-280a-414d-b0ab-8b11c4ac1bb7</id>
+ <updated>2010-09-02T18:30:02Z</updated>
+ <summary>Some text.</summary>
+ </entry>
+
+</feed>
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
--- /dev/null
+++ b/comm/suite/browser/test/mochitest/ctxmenu-image.png
Binary files 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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=377611
+-->
+ <head>
+ <title>Test for feed discovery</title>
+
+ <!-- Straight up standard -->
+ <link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" />
+ <link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" />
+ <link rel="feed" title="3" href="/3.xml" />
+
+ <!-- rel is a space-separated list -->
+ <link rel=" alternate " type="application/atom+xml" title="4" href="/4.atom" />
+ <link rel="foo alternate" type="application/atom+xml" title="5" href="/5.atom" />
+ <link rel="alternate foo" type="application/atom+xml" title="6" href="/6.atom" />
+ <link rel="foo alternate foo" type="application/atom+xml" title="7" href="/7.atom" />
+ <link rel="meat feed cake" title="8" href="/8.atom" />
+
+ <!-- rel is case-insensitive -->
+ <link rel="ALTERNate" type="application/atom+xml" title="9" href="/9.atom" />
+ <link rel="fEEd" title="10" href="/10.atom" />
+
+ <!-- type can have leading and trailing whitespace -->
+ <link rel="alternate" type=" application/atom+xml " title="11" href="/11.atom" />
+
+ <!-- type is case-insensitive -->
+ <link rel="alternate" type="aPPliCAtion/ATom+xML" title="12" href="/12.atom" />
+
+ <!-- "feed stylesheet" is a feed, though "alternate stylesheet" isn't -->
+ <link rel="feed stylesheet" title="13" href="/13.atom" />
+
+ <!-- hyphens or letters around rel not allowed -->
+ <link rel="disabled-alternate" type="application/atom+xml" title="Bogus1" href="/Bogus1" />
+ <link rel="alternates" type="application/atom+xml" title="Bogus2" href="/Bogus2" />
+ <link rel=" alternate-like" type="application/atom+xml" title="Bogus3" href="/Bogus3" />
+
+ <!-- don't tolerate text/xml if title includes 'rss' not as a word -->
+ <link rel="alternate" type="text/xml" title="Bogus4 scissorsshaped" href="/Bogus4" />
+
+ <!-- don't tolerate application/xml if title includes 'rss' not as a word -->
+ <link rel="alternate" type="application/xml" title="Bogus5 scissorsshaped" href="/Bogus5" />
+
+ <!-- don't tolerate application/rdf+xml if title includes 'rss' not as a word -->
+ <link rel="alternate" type="application/rdf+xml" title="Bogus6 scissorsshaped" href="/Bogus6" />
+
+ <!-- don't tolerate random types -->
+ <link rel="alternate" type="text/plain" title="Bogus7 rss" href="/Bogus7" />
+
+ <!-- don't find Atom by title -->
+ <link rel="foopy" type="application/atom+xml" title="Bogus8 Atom and RSS" href="/Bogus8" />
+
+ <!-- don't find application/rss+xml by title -->
+ <link rel="goats" type="application/rss+xml" title="Bogus9 RSS and Atom" href="/Bogus9" />
+
+ <!-- don't find application/rdf+xml by title -->
+ <link rel="alternate" type="application/rdf+xml" title="Bogus10 RSS and Atom" href="/Bogus10" />
+
+ <!-- don't find application/xml by title -->
+ <link rel="alternate" type="application/xml" title="Bogus11 RSS and Atom" href="/Bogus11" />
+
+ <!-- don't find text/xml by title -->
+ <link rel="alternate" type="text/xml" title="Bogus12 RSS and Atom" href="/Bogus12" />
+
+ <!-- alternate and stylesheet isn't a feed -->
+ <link rel="alternate stylesheet" type="application/rss+xml" title="Bogus13 RSS" href="/Bogus13" />
+ </head>
+ <body>
+ <script>
+ window.onload = function() {
+ netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+ var tests = new Array();
+
+ var currentWindow =
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ var browserSH = currentWindow.XULBrowserWindow;
+
+ var discovered = browserSH.feeds;
+ tests.push({ check: discovered.length > 0,
+ message: "some feeds should be discovered" });
+
+ var feeds = [];
+
+ for (var aFeed of discovered) {
+ feeds[aFeed.href] = true;
+ }
+
+ for (var aLink of document.getElementsByTagName("link")) {
+ // ignore real stylesheets, and anything without an href property
+ if (aLink.type != "text/css" && aLink.href) {
+ if (/bogus/i.test(aLink.title)) {
+ tests.push({ check: !feeds[aLink.href], todo: /todo/i.test(aLink.title),
+ message: "don't discover " + aLink.href });
+ } else {
+ tests.push({ check: feeds[aLink.href], todo: /todo/i.test(aLink.title),
+ message: "should discover " + aLink.href });
+ }
+ }
+ }
+ window.arguments[0].tests = tests;
+ window.close();
+ }
+ </script>
+ </body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Subtest for browser context menu</title>
+</head>
+<body>
+Browser context menu subtest.
+
+<div id="test-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
+<a id="test-link" href="http://mozilla.com">Click the monkey!</a>
+<a id="test-mailto" href="mailto:codemonkey@mozilla.com">Mail the monkey!</a><br>
+<input id="test-input"><br>
+<input id="test-input-select"><br>
+<img id="test-image" src="ctxmenu-image.png">
+<canvas id="test-canvas" width="100" height="100" style="background-color: blue"></canvas>
+<video controls id="test-video-ok" src="video.ogg" width="100" height="100" style="background-color: green"></video>
+<video id="test-audio-in-video" src="audio.ogg" width="100" height="100" style="background-color: red"></video>
+<video controls id="test-video-bad" src="bogus.duh" width="100" height="100" style="background-color: orange"></video>
+<video controls id="test-video-bad2" width="100" height="100" style="background-color: yellow">
+ <source src="bogus.duh" type="video/durrrr;">
+</video>
+<iframe id="test-iframe" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-video-in-iframe" src="video.ogg" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-image-in-iframe" src="ctxmenu-image.png" width="98" height="98" style="border: 1px solid black"></iframe>
+<textarea id="test-textarea">chssseesbbbie</textarea> <!-- a weird word which generates only one suggestion -->
+<textarea id="test-textarea-sel">test</textarea>
+<div id="test-contenteditable" contenteditable="true">chssseefsbbbie</div> <!-- a more weird word which generates no suggestions -->
+<input id="test-input-spellcheck" type="text" spellcheck="true" autofocus value="prodkjfgigrty"> <!-- this one also generates one suggestion -->
+<div contextmenu="myMenu">
+ <p id="test-pagemenu" hopeless="true">I've got a context menu!</p>
+ <menu id="myMenu" type="context">
+ <menuitem label="Plain item" onclick="document.getElementById('test-pagemenu').removeAttribute('hopeless');"></menuitem>
+ <menuitem label="Disabled item" disabled></menuitem>
+ <menuitem> Item w/ textContent</menuitem>
+ <menu>
+ <menuitem type="checkbox" label="Checkbox" checked></menuitem>
+ </menu>
+ <menu>
+ <menuitem type="radio" label="Radio1" checked></menuitem>
+ <menuitem type="radio" label="Radio2"></menuitem>
+ <menuitem type="radio" label="Radio3"></menuitem>
+ </menu>
+ <menu>
+ <menuitem label="Item w/ icon" icon="favicon.ico"></menuitem>
+ <menuitem label="Item w/ bad icon" icon="data://www.mozilla.org/favicon.ico"></menuitem>
+ </menu>
+ <menu label="Submenu">
+ <menuitem type="radio" label="Radio1" radiogroup="rg"></menuitem>
+ <menuitem type="radio" label="Radio2" checked radiogroup="rg"></menuitem>
+ <menuitem type="radio" label="Radio3" radiogroup="rg"></menuitem>
+ <menu>
+ <menuitem type="checkbox" label="Checkbox"></menuitem>
+ </menu>
+ </menu>
+ <menu hidden>
+ <menuitem label="Bogus item"></menuitem>
+ </menu>
+ <menu>
+ </menu>
+ <menuitem label="Hidden item" hidden></menuitem>
+ <menuitem></menuitem>
+ </menu>
+</div>
+
+<!-- SeaMonkey specific elements -->
+<a href="http://mozilla.com"><img id="test-image-link" alt="Click the monkey!" src="ctxmenu-image.png"></a>
+<a href="mailto:codemonkey@mozilla.com"><img id="test-image-mailto" alt="Mail the monkey!" src="ctxmenu-image.png"></a><br>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=364677
+-->
+<head>
+ <title>Test for Bug 364677</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=364677">Mozilla Bug 364677</a>
+<p id="display"><iframe id="testFrame" src="bug364677-data.xml"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody">
+
+/** Test for Bug 364677 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ is(SpecialPowers.wrap($("testFrame")).contentDocument.documentElement.id, "feedHandler",
+ "Feed served as text/xml without a channel/link should have been sniffed");
+});
+addLoadEvent(SimpleTest.finish);
+</script>
+</pre>
+</body>
+</html>
+
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=395533
+-->
+<head>
+ <title>Test for Bug 395533</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=395533">Mozilla Bug 395533</a>
+<p id="display"><iframe id="testFrame" src="bug395533-data.txt"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody">
+
+/** Test for Bug 395533 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Need privs because the feed seems to have an about:feeds principal or some
+ // such. It's not same-origin with us in any case.
+ netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+ is($("testFrame").contentDocument.documentElement.id, "",
+ "Text got sniffed as a feed?");
+});
+addLoadEvent(SimpleTest.finish);
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=436801
+-->
+<head>
+ <title>Test feed preview subscribe UI</title>
+ <script src="/MochiKit/packed.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=436801">Mozilla Bug 436801</a>
+<p id="display"><iframe id="testFrame" src="bug436801-data.xml"></iframe></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function () {
+ var doc = SpecialPowers.wrap($("testFrame")).contentDocument;
+
+ checkNode(doc.getElementById("feedTitleText"), [
+ "ELEMENT", "h1", { "xml:base": "http://www.example.com/foo/bar/" }, [
+ ["TEXT", "Example of a "],
+ ["ELEMENT", "em", [
+ ["TEXT", "special"],
+ ]],
+ ["TEXT", " feed ("],
+ ["ELEMENT", "img", { "src": "baz.png" }],
+ ["TEXT", ")"],
+ ]
+ ]);
+
+ checkNode(doc.getElementById("feedSubtitleText"), [
+ "ELEMENT", "h2", { "xml:base": "http://www.example.com/foo/bar/" }, [
+ ["TEXT", "With a "],
+ ["ELEMENT", "em", [
+ ["TEXT", "special"],
+ ]],
+ ["TEXT", " subtitle ("],
+ ["ELEMENT", "img", { "src": "baz.png" }],
+ ["TEXT", ")"],
+ ]
+ ]);
+
+ checkNode(doc.querySelector(".entry").firstChild.firstChild.firstChild, [
+ "ELEMENT", "span", { "xml:base": "http://www.example.com/foo/bar/" }, [
+ ["TEXT", "Some "],
+ ["ELEMENT", "abbr", { title: "Extensible Hyper-text Mark-up Language" }, [
+ ["TEXT", "XHTML"],
+ ]],
+ ["TEXT", " examples ("],
+ ["ELEMENT", "img", { "src": "baz.png" }],
+ ["TEXT", ")"],
+ ]
+ ]);
+
+ checkNode(doc.querySelectorAll(".entry")[1].firstChild.firstChild.firstChild, [
+ "ELEMENT", "span", { "xml:base": "http://www.example.com/foo/bar/" }, [
+ ["TEXT", "Some "],
+ ["ELEMENT", "abbr", { title: "Hyper-text Mark-up Language" }, [
+ ["TEXT", "HTML"],
+ ]],
+ ["TEXT", " examples ("],
+ ["ELEMENT", "img", { "src": "baz.png" }],
+ ["TEXT", ")"],
+ ]
+ ]);
+});
+
+addLoadEvent(SimpleTest.finish);
+
+function checkNode(node, schema) {
+ var typeName = schema.shift() + "_NODE";
+ var type = Node[typeName];
+ is(node.nodeType, type, "Node should be expected type " + typeName);
+ if (type == Node.TEXT_NODE) {
+ var text = schema.shift();
+ is(node.data, text, "Text should match");
+ return;
+ }
+ // type == Node.ELEMENT_NODE
+ var tag = schema.shift();
+ is(node.localName, tag, "Element should have expected tag");
+ while (schema.length) {
+ var val = schema.shift();
+ if (Array.isArray(val))
+ var childSchema = val;
+ else
+ var attrSchema = val;
+ }
+ if (attrSchema) {
+ var nsTable = {
+ xml: "http://www.w3.org/XML/1998/namespace",
+ };
+ for (var name in attrSchema) {
+ var [ns, nsName] = name.split(":");
+ var val = nsName ? node.getAttributeNS(nsTable[ns], nsName) :
+ node.getAttribute(name);
+ is(val, attrSchema[name], "Attribute " + name + " should match");
+ }
+ }
+ if (childSchema) {
+ var numChildren = node.childNodes.length;
+ is(childSchema.length, numChildren,
+ "Element should have expected number of children");
+ for (var i = 0; i < numChildren; i++)
+ checkNode(node.childNodes[i], childSchema[i]);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tests for browser context menu</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+Browser context menu tests.
+<p id="display"></p>
+
+<div id="content">
+</div>
+
+<pre id="test">
+<script class="testbody">
+
+/** Test for Login Manager: multiple login autocomplete. **/
+
+SpecialPowers.ChromeUtils.import("resource://gre/modules/InlineSpellChecker.jsm", window);
+SpecialPowers.ChromeUtils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window);
+
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+
+function openContextMenuFor(element, shiftkey, waitForSpellCheck) {
+ // Context menu should be closed before we open it again.
+ is(SpecialPowers.wrap(contextMenu).state, "closed", "checking if popup is closed");
+
+ if (lastElement)
+ lastElement.blur();
+ element.focus();
+
+ // Some elements need time to focus and spellcheck before any tests are
+ // run on them.
+ function actuallyOpenContextMenuFor() {
+ lastElement = element;
+ var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey };
+ synthesizeMouse(element, 2, 2, eventDetails, element.ownerDocument.defaultView);
+ }
+
+ if (waitForSpellCheck)
+ onSpellCheck(element, actuallyOpenContextMenuFor);
+ else
+ actuallyOpenContextMenuFor();
+}
+
+function closeContextMenu() {
+ contextMenu.hidePopup();
+}
+
+function executeCopyCommand(command, expectedValue)
+{
+ // Just execute the command directly rather than simulating a context menu
+ // press to avoid having to deal with its asynchronous nature
+ SpecialPowers.wrap(subwindow).controllers.getControllerForCommand(command).doCommand(command);
+
+ // The easiest way to check the clipboard is to paste the contents into a
+ // textbox
+ input.focus();
+ input.value = "";
+ SpecialPowers.wrap(input).controllers.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
+ is(input.value, expectedValue, "paste for command " + command);
+}
+
+function invokeItemAction(generatedItemId)
+{
+ var item = contextMenu.getElementsByAttribute("generateditemid",
+ generatedItemId)[0];
+ ok(item, "Got generated XUL menu item");
+ item.doCommand();
+ is(pagemenu.hasAttribute("hopeless"), false, "attribute got removed");
+}
+
+function getVisibleMenuItems(aMenu, aData) {
+ var items = [];
+ var accessKeys = {};
+ for (var i = 0; i < aMenu.childNodes.length; i++) {
+ var item = aMenu.childNodes[i];
+ if (item.hidden)
+ continue;
+
+ var key = item.accessKey;
+ if (key)
+ key = key.toLowerCase();
+
+ var isGenerated = item.hasAttribute("generateditemid");
+
+ if (item.nodeName == "menuitem") {
+ var isSpellSuggestion = item.className == "spell-suggestion";
+ if (isSpellSuggestion) {
+ is(item.id, "", "child menuitem #" + i + " is a spelling suggestion");
+ } else if (isGenerated) {
+ is(item.id, "", "child menuitem #" + i + " is a generated item");
+ } else {
+ ok(item.id, "child menuitem #" + i + " has an ID");
+ }
+ var label = item.getAttribute("label");
+ ok(label.length, "menuitem " + item.id + " has a label");
+ if (isSpellSuggestion) {
+ is(key, "", "Spell suggestions shouldn't have an access key");
+ items.push("*" + label);
+ } else if (isGenerated) {
+ items.push("+" + label);
+ } else if (item.id.indexOf("spell-check-dictionary-") != 0 &&
+ item.id != "spell-no-suggestions") {
+ ok(key, "menuitem " + item.id + " has an access key");
+ if (accessKeys[key])
+ ok(false, "menuitem " + item.id + " has same accesskey as " + accessKeys[key]);
+ else
+ accessKeys[key] = item.id;
+ }
+ if (!isSpellSuggestion && !isGenerated) {
+ items.push(item.id);
+ }
+ if (isGenerated) {
+ var p = {};
+ p.type = item.getAttribute("type");
+ p.icon = item.getAttribute("image");
+ p.checked = item.hasAttribute("checked");
+ p.disabled = item.hasAttribute("disabled");
+ items.push(p);
+ } else {
+ items.push(!item.disabled);
+ }
+ } else if (item.nodeName == "menuseparator") {
+ ok(true, "--- seperator id is " + item.id);
+ items.push("---");
+ items.push(null);
+ } else if (item.nodeName == "menu") {
+ if (isGenerated) {
+ item.id = "generated-submenu-" + aData.generatedSubmenuId++;
+ }
+ ok(item.id, "child menu #" + i + " has an ID");
+ if (!isGenerated) {
+ ok(key, "menu has an access key");
+ if (accessKeys[key])
+ ok(false, "menu " + item.id + " has same accesskey as " + accessKeys[key]);
+ else
+ accessKeys[key] = item.id;
+ }
+ items.push(item.id);
+ items.push(!item.disabled);
+ // Add a dummy item to that the indexes in checkMenu are the same
+ // for expectedItems and actualItems.
+ items.push([]);
+ items.push(null);
+ } else {
+ ok(false, "child #" + i + " of menu ID " + aMenu.id +
+ " has an unknown type (" + item.nodeName + ")");
+ }
+ }
+ return items;
+}
+
+function checkContextMenu(expectedItems) {
+ is(contextMenu.state, "open", "checking if popup is open");
+ var data = { generatedSubmenuId: 1 };
+ checkMenu(contextMenu, expectedItems, data);
+}
+
+/*
+ * checkMenu - checks to see if the specified <menupopup> contains the
+ * expected items and state.
+ * expectedItems is a array of (1) item IDs and (2) a boolean specifying if
+ * the item is enabled or not (or null to ignore it). Submenus can be checked
+ * by providing a nested array entry after the expected <menu> ID.
+ * For example: ["blah", true, // item enabled
+ * "submenu", null, // submenu
+ * ["sub1", true, // submenu contents
+ * "sub2", false], null, // submenu contents
+ * "lol", false] // item disabled
+ *
+ */
+function checkMenu(menu, expectedItems, data) {
+ var actualItems = getVisibleMenuItems(menu, data);
+ //ok(false, "Items are: " + actualItems);
+ for (var i = 0; i < expectedItems.length; i+=2) {
+ var actualItem = actualItems[i];
+ var actualEnabled = actualItems[i + 1];
+ var expectedItem = expectedItems[i];
+ var expectedEnabled = expectedItems[i + 1];
+ if (expectedItem instanceof Array) {
+ ok(true, "Checking submenu...");
+ var menuID = expectedItems[i - 2]; // The last item was the menu ID.
+ var submenu = menu.getElementsByAttribute("id", menuID)[0];
+ ok(submenu && submenu.nodeName == "menu", "got expected submenu element");
+ checkMenu(submenu.menupopup, expectedItem, data);
+ } else {
+ is(actualItem, expectedItem,
+ "checking item #" + i/2 + " (" + expectedItem + ") name");
+
+ if (typeof expectedEnabled == "object" && expectedEnabled != null ||
+ typeof actualEnabled == "object" && actualEnabled != null) {
+
+ ok(!(actualEnabled == null), "actualEnabled is not null");
+ ok(!(expectedEnabled == null), "expectedEnabled is not null");
+ is(typeof actualEnabled, typeof expectedEnabled, "checking types");
+
+ if (typeof actualEnabled != typeof expectedEnabled ||
+ actualEnabled == null || expectedEnabled == null)
+ continue;
+
+ is(actualEnabled.type, expectedEnabled.type,
+ "checking item #" + i/2 + " (" + expectedItem + ") type attr value");
+ var icon = actualEnabled.icon;
+ if (icon) {
+ var tmp = "";
+ var j = icon.length - 1;
+ while (j && icon[j] != "/") {
+ tmp = icon[j--] + tmp;
+ }
+ icon = tmp;
+ }
+ is(icon, expectedEnabled.icon,
+ "checking item #" + i/2 + " (" + expectedItem + ") icon attr value");
+ is(actualEnabled.checked, expectedEnabled.checked,
+ "checking item #" + i/2 + " (" + expectedItem + ") has checked attr");
+ is(actualEnabled.disabled, expectedEnabled.disabled,
+ "checking item #" + i/2 + " (" + expectedItem + ") has disabled attr");
+ } else if (expectedEnabled != null)
+ is(actualEnabled, expectedEnabled,
+ "checking item #" + i/2 + " (" + expectedItem + ") enabled state");
+ }
+ }
+ // Could find unexpected extra items at the end...
+ is(actualItems.length, expectedItems.length, "checking expected number of menu entries");
+}
+
+/*
+ * runTest
+ *
+ * Called by a popupshowing event handler. Each test checks for expected menu
+ * contents, closes the popup, and finally triggers the popup on a new element
+ * (thus kicking off another cycle).
+ *
+ */
+function runTest(testNum) {
+ ok(true, "Starting test #" + testNum);
+
+ switch (testNum) {
+ case 1:
+ // Invoke context menu for next test.
+ openContextMenuFor(text);
+ break;
+
+ case 2:
+ // Context menu for plain text
+ plainTextItems = ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-stop", false,
+ "---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "context-sendpage", true,
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true];
+ checkContextMenu(plainTextItems);
+ closeContextMenu();
+ openContextMenuFor(link); // Invoke context menu for next test.
+ break;
+
+ case 3:
+ // Context menu for text link
+ checkContextMenu(["context-openlinkintab", true,
+ "context-openlink", true,
+ "context-openlinkinprivatewindow", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ "context-sendlink", true,
+ "context-copylink", true,
+ "---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "---", null,
+ "context-metadata", true]);
+ closeContextMenu();
+ openContextMenuFor(mailto); // Invoke context menu for next test.
+ break;
+
+ case 4:
+ // Context menu for text mailto-link
+ checkContextMenu(["context-copyemail", true,
+ "context-copylink", true,
+ "---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "---", null,
+ "context-metadata", true]);
+ closeContextMenu();
+ openContextMenuFor(input); // Invoke context menu for next test.
+ break;
+
+ case 5:
+ // Context menu for text input field
+ checkContextMenu(["context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", false,
+ "context-copy", false,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", false,
+ "---", null,
+ "spell-check-enabled", true]);
+ closeContextMenu();
+ input_sel.value = "test";
+ input_sel.select();
+ openContextMenuFor(input_sel); // Invoke context menu for next test.
+ break;
+
+ case 6:
+ // Context menu for text input field with text
+ checkContextMenu(["context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", true,
+ "---", null,
+ "context-selectall", true,
+ "context-searchselect", true,
+ "---", null,
+ "spell-check-enabled", true]);
+ closeContextMenu();
+ openContextMenuFor(img); // Invoke context menu for next test.
+ break;
+
+ case 7:
+ // Context menu for an image
+ checkContextMenu(["context-reloadimage", true,
+ "context-viewimage", true,
+ "context-blockimage", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true].concat(
+ ("@mozilla.org/suite/shell-service;1" in Cc) ?
+ ["context-setDesktopBackground", true] : []).concat(
+ ["---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "context-sendpage", true,
+ "---", null,
+ "context-metadata", true]));
+ closeContextMenu();
+ openContextMenuFor(canvas); // Invoke context menu for next test.
+ break;
+
+ case 8:
+ // Context menu for a canvas
+ checkContextMenu(["context-viewimage", true,
+ "context-saveimage", true,
+ "context-bookmarkpage", true,
+ "context-sendpage", true,
+ "context-selectall", true]);
+ closeContextMenu();
+ openContextMenuFor(video_ok); // Invoke context menu for next test.
+ break;
+
+ case 9:
+ // Context menu for a video (with a VALID media source)
+ checkContextMenu(["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", true,
+ ["context-media-playbackrate-050", true,
+ "context-media-playbackrate-100", true,
+ "context-media-playbackrate-150", true,
+ "context-media-playbackrate-200", true], null,
+ "context-media-hidecontrols", true,
+ "context-video-showstats", true,
+ "context-video-fullscreen", true,
+ "---", null,
+ "context-viewvideo", true,
+ "context-copyvideourl", true,
+ "---", null,
+ "context-savevideo", true,
+ "context-sendvideo", true,
+ "context-video-saveimage", true]);
+ closeContextMenu();
+ openContextMenuFor(audio_in_video); // Invoke context menu for next test.
+ break;
+
+ case 10:
+ // Context menu for a video (with an audio-only file)
+ checkContextMenu(["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", true,
+ ["context-media-playbackrate-050", true,
+ "context-media-playbackrate-100", true,
+ "context-media-playbackrate-150", true,
+ "context-media-playbackrate-200", true], null,
+ "context-media-showcontrols", true,
+ "---", null,
+ "context-copyaudiourl", true,
+ "---", null,
+ "context-saveaudio", true,
+ "context-sendaudio", true]);
+ closeContextMenu();
+ openContextMenuFor(video_bad); // Invoke context menu for next test.
+ break;
+
+ case 11:
+ // Context menu for a video (with an INVALID media source)
+ checkContextMenu(["context-media-play", false,
+ "context-media-mute", false,
+ "context-media-playbackrate", false,
+ ["context-media-playbackrate-050", null,
+ "context-media-playbackrate-100", null,
+ "context-media-playbackrate-150", null,
+ "context-media-playbackrate-200", null], null,
+ "context-media-hidecontrols", false,
+ "context-video-showstats", false,
+ "context-video-fullscreen", false,
+ "---", null,
+ "context-viewvideo", true,
+ "context-copyvideourl", true,
+ "---", null,
+ "context-savevideo", true,
+ "context-sendvideo", true,
+ "context-video-saveimage", false]);
+ closeContextMenu();
+ openContextMenuFor(video_bad2); // Invoke context menu for next test.
+ break;
+
+ case 12:
+ // Context menu for a video (with an INVALID media source)
+ checkContextMenu(["context-media-play", false,
+ "context-media-mute", false,
+ "context-media-playbackrate", false,
+ ["context-media-playbackrate-050", null,
+ "context-media-playbackrate-100", null,
+ "context-media-playbackrate-150", null,
+ "context-media-playbackrate-200", null], null,
+ "context-media-hidecontrols", false,
+ "context-video-showstats", false,
+ "context-video-fullscreen", false,
+ "---", null,
+ "context-viewvideo", false,
+ "context-copyvideourl", false,
+ "---", null,
+ "context-savevideo", false,
+ "context-sendvideo", false,
+ "context-video-saveimage", false]);
+ closeContextMenu();
+ openContextMenuFor(iframe); // Invoke context menu for next test.
+ break;
+
+ case 13:
+ // Context menu for an iframe
+ checkContextMenu(["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-stop", false,
+ "---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "context-sendpage", true,
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true,
+ "---", null,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "context-sendframe", true,
+ "---", null,
+ "context-viewframesource", true,
+ "context-viewframeinfo", true], null]);
+ closeContextMenu();
+ openContextMenuFor(video_in_iframe); // Invoke context menu for next test.
+ break;
+
+ case 14:
+ // Context menu for a video in an iframe
+ checkContextMenu(["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", true,
+ ["context-media-playbackrate-050", true,
+ "context-media-playbackrate-100", true,
+ "context-media-playbackrate-150", true,
+ "context-media-playbackrate-200", true], null,
+ "context-media-hidecontrols", true,
+ "context-video-showstats", true,
+ "context-video-fullscreen", true,
+ "---", null,
+ "context-viewvideo", true,
+ "context-copyvideourl", true,
+ "---", null,
+ "context-savevideo", true,
+ "context-sendvideo", true,
+ "context-video-saveimage", true,
+ "---", null,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "context-sendframe", true,
+ "---", null,
+ "context-viewframesource", true,
+ "context-viewframeinfo", true], null]);
+ closeContextMenu();
+ openContextMenuFor(image_in_iframe); // Invoke context menu for next test.
+ break;
+
+ case 15:
+ // Context menu for an image in an iframe
+ checkContextMenu(["context-reloadimage", true,
+ "context-viewimage", true,
+ "context-blockimage", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ ].concat(
+ ("@mozilla.org/suite/shell-service;1" in Cc) ?
+ ["context-setDesktopBackground", true] : [])
+ .concat(
+ ["---", null,
+ "context-sendpage", true,
+ "---", null,
+ "context-metadata", true,
+ "---", null,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "context-sendframe", true,
+ "---", null,
+ "context-viewframesource", true,
+ "context-viewframeinfo", true], null]));
+ closeContextMenu();
+ openContextMenuFor(text); // Invoke context menu for next test.
+ break;
+
+ case 16:
+ // Re-check context menu for plain text to make sure it hasn't changed
+ checkContextMenu(plainTextItems);
+ closeContextMenu();
+ textarea_sel.select();
+ openContextMenuFor(textarea_sel, false, true);
+ break;
+
+ case 17:
+ // search for text with text area's selected value
+ checkContextMenu(["context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", true,
+ "---", null,
+ "context-selectall", true,
+ "context-searchselect", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null]);
+ closeContextMenu();
+ openContextMenuFor(textarea, false, true); // Invoke context menu for next test, but wait for the spellcheck.
+ break;
+
+ case 18:
+ // Context menu for textarea
+ checkContextMenu(["*chubbiness", true, // spelling suggestion
+ "---", null,
+ "spell-add-to-dictionary", true,
+ "spell-ignore-word", true,
+ "---", null,
+ "context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", false,
+ "context-copy", false,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null]);
+
+ contextMenu.ownerDocument.getElementById("spell-add-to-dictionary").doCommand(); // Add to dictionary
+ closeContextMenu();
+ openContextMenuFor(textarea, false, true); // Invoke context menu for next test.
+ break;
+
+ case 19:
+ // Context menu for textarea after a word has been added
+ // to the dictionary
+ checkContextMenu(["spell-undo-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", false,
+ "context-copy", false,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null,
+ ]);
+
+ contextMenu.ownerDocument.getElementById("spell-undo-add-to-dictionary").doCommand(); // Undo add to dictionary
+ closeContextMenu();
+ openContextMenuFor(contenteditable, false, true);
+ break;
+
+ case 20:
+ // Context menu for contenteditable
+ checkContextMenu(["spell-no-suggestions", false,
+ "---", null,
+ "spell-add-to-dictionary", true,
+ "spell-ignore-word", true,
+ "---", null,
+ "context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", false,
+ "context-copy", false,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null]);
+
+ closeContextMenu();
+ openContextMenuFor(inputspell, false, true); // Invoke context menu for next test.
+ break;
+
+ case 21:
+ // Context menu for spell-check input
+ checkContextMenu(["*prodigality", true, // spelling suggestion
+ "---", null,
+ "spell-add-to-dictionary", true,
+ "spell-ignore-word", true,
+ "---", null,
+ "context-undo", false,
+ "context-redo", false,
+ "---", null,
+ "context-cut", false,
+ "context-copy", false,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null]);
+
+ closeContextMenu();
+ openContextMenuFor(link); // Invoke context menu for next test.
+ break;
+
+ case 22:
+ executeCopyCommand("cmd_copyLink", "http://mozilla.com/");
+ closeContextMenu();
+ openContextMenuFor(pagemenu); // Invoke context menu for next test.
+ break;
+
+ case 23:
+ // Context menu for element with assigned content context menu
+ checkContextMenu(["+Plain item", {type: "", icon: "", checked: false, disabled: false},
+ "+Disabled item", {type: "", icon: "", checked: false, disabled: true},
+ "+Item w/ textContent", {type: "", icon: "", checked: false, disabled: false},
+ "---", null,
+ "+Checkbox", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "---", null,
+ "+Radio1", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "+Radio2", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "+Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "---", null,
+ "+Item w/ icon", {type: "", icon: "favicon.ico", checked: false, disabled: false},
+ "+Item w/ bad icon", {type: "", icon: "", checked: false, disabled: false},
+ "---", null,
+ "generated-submenu-1", true,
+ ["+Radio1", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "+Radio2", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "+Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "---", null,
+ "+Checkbox", {type: "checkbox", icon: "", checked: false, disabled: false}], null,
+ "---", null,
+ "context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-stop", false,
+ "---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "context-sendpage", true,
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true]);
+
+ invokeItemAction("0");
+ closeContextMenu();
+ openContextMenuFor(pagemenu, true); // Invoke context menu for next test.
+ break;
+
+ case 24:
+ // Context menu for element with assigned content context menu
+ // The shift key should bypass content context menu processing
+ checkContextMenu(["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-stop", false,
+ "---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "context-sendpage", true,
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true]);
+ closeContextMenu();
+
+ // Continue with SeaMonkey specific cases.
+ openContextMenuFor(img_link); // Invoke context menu for next test.
+ break;
+
+ case 25:
+ // Context menu for an image with a link
+ checkContextMenu(["context-openlinkintab", true,
+ "context-openlink", true,
+ "context-openlinkinprivatewindow", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ "context-sendlink", true,
+ "context-copylink", true,
+ "---", null,
+ "context-reloadimage", true,
+ "context-viewimage", true,
+ "context-blockimage", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true].concat(
+ ("@mozilla.org/suite/shell-service;1" in Cc) ?
+ ["context-setDesktopBackground", true] : []).concat(
+ ["---", null,
+ "context-bookmarkpage", true,
+ "---", null,
+ "context-metadata", true]));
+ closeContextMenu();
+ openContextMenuFor(img_mailto); // Invoke context menu for next test.
+ break;
+
+ case 26:
+ // Context menu for an image with a mailto: link
+ checkContextMenu(["context-copyemail", true,
+ "context-copylink", true,
+ "---", null,
+ "context-reloadimage", true,
+ "context-viewimage", true,
+ "context-blockimage", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true].concat(
+ ("@mozilla.org/suite/shell-service;1" in Cc) ?
+ ["context-setDesktopBackground", true] : []).concat(
+ ["---", null,
+ "context-bookmarkpage", true,
+ "---", null,
+ "context-metadata", true]));
+ closeContextMenu();
+
+ subwindow.close();
+ SimpleTest.finish();
+ return;
+
+ /*
+ * Other things that would be nice to test:
+ * - selected text
+ * - spelling / misspelled word (in text input?)
+ * - check state of disabled items
+ * - test execution of menu items (maybe as a separate test?)
+ */
+
+ default:
+ ok(false, "Unexpected invocation of test #" + testNum);
+ subwindow.close();
+ SimpleTest.finish();
+ return;
+ }
+
+}
+
+
+var testNum = 1;
+var subwindow, chromeWin, contextMenu, lastElement;
+var text, link, mailto, input, img, canvas, video_ok, video_bad, video_bad2,
+ iframe, video_in_iframe, image_in_iframe, textarea, contenteditable,
+ inputspell, pagemenu, audio_in_video;
+
+// SeaMonkey specific variables.
+var img_link, img_mailto;
+
+function startTest() {
+ chromeWin = SpecialPowers.wrap(subwindow)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .QueryInterface(Ci.nsIDOMChromeWindow);
+ contextMenu = chromeWin.document.getElementById("contentAreaContextMenu");
+ ok(contextMenu, "Got context menu XUL");
+
+ if (chromeWin.document.getElementById("context-stop").getAttribute("disabled") != "true") {
+ isnot(false, "Document still loading");
+ SimpleTest.executeSoon(startTest);
+ return;
+ }
+
+ lastElement = null;
+
+ text = subwindow.document.getElementById("test-text");
+ link = subwindow.document.getElementById("test-link");
+ mailto = subwindow.document.getElementById("test-mailto");
+ input = subwindow.document.getElementById("test-input");
+ input_sel = subwindow.document.getElementById("test-input-select");
+ img = subwindow.document.getElementById("test-image");
+ canvas = subwindow.document.getElementById("test-canvas");
+ video_ok = subwindow.document.getElementById("test-video-ok");
+ audio_in_video = subwindow.document.getElementById("test-audio-in-video");
+ video_bad = subwindow.document.getElementById("test-video-bad");
+ video_bad2 = subwindow.document.getElementById("test-video-bad2");
+ iframe = subwindow.document.getElementById("test-iframe");
+ video_in_iframe = subwindow.document.getElementById("test-video-in-iframe").contentDocument.getElementsByTagName("video")[0];
+ // Ensure that contextmenu has 'context-media-play' item when check runs.
+ video_in_iframe.pause();
+ image_in_iframe = subwindow.document.getElementById("test-image-in-iframe").contentDocument.getElementsByTagName("img")[0];
+ textarea = subwindow.document.getElementById("test-textarea");
+ textarea_sel = subwindow.document.getElementById("test-textarea-sel");
+ contenteditable = subwindow.document.getElementById("test-contenteditable");
+ contenteditable.focus(); // content editable needs to be focused to enable spellcheck
+ inputspell = subwindow.document.getElementById("test-input-spellcheck");
+ pagemenu = subwindow.document.getElementById("test-pagemenu");
+
+ // SeaMonkey specific elements.
+ img_link = subwindow.document.getElementById("test-image-link");
+ img_mailto = subwindow.document.getElementById("test-image-mailto");
+
+ contextMenu.addEventListener("popupshown", function() { runTest(++testNum); });
+ runTest(1);
+}
+
+// We open this in a separate window, because the Mochitests run inside a frame.
+// The frame causes an extra menu item, and prevents running the test
+// standalone (ie, clicking the test name in the Mochitest window) to see
+// success/failure messages.
+var painted = false, loaded = false;
+
+function waitForEvents(event)
+{
+ if (event.type == "MozAfterPaint")
+ painted = true;
+ else if (event.type == "load")
+ loaded = true;
+ if (painted && loaded) {
+ subwindow.removeEventListener("MozAfterPaint", waitForEvents);
+ subwindow.onload = null;
+ startTest();
+ }
+}
+
+var subwindow = window.open("./subtst_contextmenu.html", "contextmenu-subtext", "width=600,height=700");
+subwindow.addEventListener("MozAfterPaint", waitForEvents);
+subwindow.onload = waitForEvents;
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=240393
+-->
+<head>
+ <title>Test for feed discovery</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=377611">Mozilla Bug 377611</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody">
+
+/** Tests for bug 240393 (from bug 377611 on Firefox side) **/
+
+var rv = { tests: null };
+var testCheckInterval = null;
+
+function startTest() {
+ var url = window.location.href.replace(/test_feed_discovery\.html/,
+ 'feed_discovery.html');
+ SpecialPowers.openDialog(window, [url, '', 'dialog=no,width=10,height=10', rv]);
+ testCheckInterval = window.setInterval(tryIfTestIsFinished, 500);
+}
+
+function tryIfTestIsFinished() {
+ if (rv.tests) {
+ window.clearInterval(testCheckInterval);
+ checkTest();
+ }
+}
+
+function checkTest() {
+ for (var i = 0; i < rv.tests.length; ++ i) {
+ var test = rv.tests[i];
+ if (test.todo)
+ todo(test.check, test.message);
+ else
+ ok(test.check, test.message);
+ }
+ SimpleTest.finish();
+}
+
+window.onload = startTest;
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=402788
+-->
+<head>
+ <title>Test for Bug 402788</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=402788">Mozilla Bug 402788</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody">
+
+/** Test for Bug 402788 **/
+
+ // return false if an exception has been catched, true otherwise
+ function testRegisterHandler(aIsProtocol, aTxt, aUri, aTitle)
+ {
+ try {
+ if (aIsProtocol)
+ navigator.registerProtocolHandler(aTxt, aUri, aTitle);
+ else
+ navigator.registerContentHandler(aTxt, aUri, aTitle);
+ }
+ catch(e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ ok(navigator.registerProtocolHandler, "navigator.registerProtocolHandler should be defined");
+ ok(navigator.registerContentHandler, "navigator.registerContentHandler should be defined");
+
+ // testing a generic case
+ is(true, testRegisterHandler(true, "foo", "http://mochi.test:8888/%s", "Foo handler"), "registering a foo protocol handler should work");
+ is(true, testRegisterHandler(false, "application/rss+xml", "http://mochi.test:8888/%s", "Foo handler"), "registering a foo content handler should work");
+
+ // testing with wrong uris
+ is(false, testRegisterHandler(true, "foo", "http://mochi.test:8888/", "Foo handler"), "a protocol handler uri should contain %s");
+ is(false, testRegisterHandler(false, "application/rss+xml", "http://mochi.test:8888/", "Foo handler"), "a content handler uri should contain %s");
+
+ // the spec says we should not throw here, but it probably needs to be changed
+ is(false, testRegisterHandler(true, "foo", "foo/%s", "Foo handler"), "a protocol handler uri should be valid");
+ is(false, testRegisterHandler(false, "application/rss+xml", "foo/%s", "Foo handler"), "a content handler uri should be valid");
+
+ // we should only accept to register when the handler has the same host as the current page (bug 402287)
+ is(false, testRegisterHandler(true, "foo", "http://remotehost:8888/%s", "Foo handler"), "registering a foo protocol handler with a different host should not work");
+ is(false, testRegisterHandler(false, "application/rss+xml", "http://remotehost:8888/%s", "Foo handler"), "registering a foo content handler with a different host should not work");
+
+ // restriction to http(s) for the uri of the handler (bug 401343)
+ // https should work (http already tested in the generic case)
+ is(true, testRegisterHandler(true, "foo", "https://mochi.test:8888/%s", "Foo handler"), "registering a foo protocol handler with https scheme should work");
+ is(true, testRegisterHandler(false, "application/rss+xml", "https://mochi.test:8888/%s", "Foo handler"), "registering a foo content handler with https scheme should work");
+ // ftp should not work
+ is(false, testRegisterHandler(true, "foo", "ftp://mochi.test:8888/%s", "Foo handler"), "registering a foo protocol handler with ftp scheme should not work");
+ is(false, testRegisterHandler(false, "application/rss+xml", "ftp://mochi.test:8888/%s", "Foo handler"), "registering a foo content handler with ftp scheme should not work");
+ // chrome should not work
+ is(false, testRegisterHandler(true, "foo", "chrome://mochi.test:8888/%s", "Foo handler"), "registering a foo protocol handler with chrome scheme should not work");
+ is(false, testRegisterHandler(false, "application/rss+xml", "chrome://mochi.test:8888/%s", "Foo handler"), "registering a foo content handler with chrome scheme should not work");
+ // foo should not work
+ is(false, testRegisterHandler(true, "foo", "foo://mochi.test:8888/%s", "Foo handler"), "registering a foo protocol handler with foo scheme should not work");
+ is(false, testRegisterHandler(false, "application/rss+xml", "foo://mochi.test:8888/%s", "Foo handler"), "registering a foo content handler with foo scheme should not work");
+
+ // for security reasons, protocol handlers should never be registered for some schemes (chrome, vbscript, ...) (bug 402788)
+ is(false, testRegisterHandler(true, "chrome", "http://mochi.test:8888/%s", "chrome handler"), "registering a chrome protocol handler should not work");
+ is(false, testRegisterHandler(true, "vbscript", "http://mochi.test:8888/%s", "vbscript handler"), "registering a vbscript protocol handler should not work");
+ is(false, testRegisterHandler(true, "javascript", "http://mochi.test:8888/%s", "javascript handler"), "registering a javascript protocol handler should not work");
+ is(false, testRegisterHandler(true, "moz-icon", "http://mochi.test:8888/%s", "moz-icon handler"), "registering a moz-icon protocol handler should not work");
+
+ // for security reasons, content handlers should never be registered for some types (html, ...)
+ is(true, testRegisterHandler(false, "application/rss+xml", "http://mochi.test:8888/%s", "Foo handler"), "registering rss content handlers should work");
+ is(true, testRegisterHandler(false, "application/atom+xml", "http://mochi.test:8888/%s", "Foo handler"), "registering atom content handlers should work");
+ todo(false, testRegisterHandler(false, "text/html", "http://mochi.test:8888/%s", "Foo handler"), "registering html content handlers should not work"); // bug 403798
+
+</script>
+</pre>
+</body>
+</html>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+
+ <title>Example Feed</title>
+ <link href="http://example.org/"/>
+ <updated>2010-08-22T18:30:02Z</updated>
+
+ <author>
+ <name>John Doe</name>
+ </author>
+ <id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
+
+ <entry>
+
+ <title>Item</title>
+ <link href="http://example.org/first"/>
+ <id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
+ <updated>2010-08-22T18:30:02Z</updated>
+
+ <summary>Some text.</summary>
+ </entry>
+
+</feed>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 512 bytes!
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ -->
+<feed xmlns="http://www.w3.org/2005/Atom">
+
+ <title>Example Feed</title>
+ <link href="http://example.org/"/>
+ <updated>2010-08-22T18:30:02Z</updated>
+
+ <author>
+ <name>John Doe</name>
+ </author>
+ <id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
+
+ <entry>
+
+ <title>Item</title>
+ <link href="http://example.org/first"/>
+ <id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
+ <updated>2010-08-22T18:30:02Z</updated>
+
+ <summary>Some text.</summary>
+ </entry>
+
+</feed>
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
--- /dev/null
+++ b/comm/suite/browser/test/mochitest/video.ogg
Binary files 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 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!DOCTYPE bindings [
+ <!ENTITY % textcontextDTD SYSTEM "chrome://communicator/locale/utilityOverlay.dtd">
+ %textcontextDTD;
+ <!ENTITY % navigatorDTD SYSTEM "chrome://navigator/locale/navigator.dtd">
+ %navigatorDTD;
+]>
+
+<bindings id="urlbarBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="urlbar" extends="chrome://global/content/autocomplete.xml#autocomplete">
+ <content sizetopopup="pref">
+ <xul:hbox class="autocomplete-textbox-container" flex="1">
+ <xul:hbox class="urlbar-security-level" flex="1" align="center" xbl:inherits="level">
+ <children includes="image|deck|stack|box">
+ <xul:image class="autocomplete-icon" allowevents="true"/>
+ </children>
+
+ <xul:hbox class="textbox-input-box paste-and-go" flex="1" tooltip="_child" xbl:inherits="context">
+ <children/>
+ <html:input anonid="input" class="autocomplete-textbox textbox-input"
+ allowevents="true"
+ xbl:inherits="value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,userAction"/>
+ <xul:tooltip anonid="tooltip"
+ onpopupshowing="document.getBindingParent(this)._showTooltip(event);"/>
+ </xul:hbox>
+ <children includes="hbox"/>
+ </xul:hbox>
+ </xul:hbox>
+
+ <xul:dropmarker class="autocomplete-history-dropmarker" allowevents="true"
+ xbl:inherits="open" anonid="historydropmarker"/>
+
+ <xul:popupset>
+ <xul:panel type="autocomplete" anonid="popup"
+ ignorekeys="true" noautofocus="true" level="top"
+ xbl:inherits="for=id,nomatch"/>
+ </xul:popupset>
+
+ <children includes="menupopup"/>
+ </content>
+
+ <implementation implements="nsIObserver, nsIDOMEventListener">
+ <constructor><![CDATA[
+ this._prefs = Services.prefs.getBranch("browser.urlbar.");
+ this._prefs.addObserver("", this);
+
+ this.updatePref("showPopup");
+ this.updatePref("autoFill");
+ this.updatePref("showSearch");
+ this.updatePref("formatting.enabled");
+ this.inputField.controllers.insertControllerAt(0, this._editItemsController);
+ this.inputField.addEventListener("overflow", this);
+ this.inputField.addEventListener("underflow", this);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ // Somehow, it's possible for the XBL destructor to fire without the
+ // constructor ever having fired. Fix:
+ if (!this._prefs) {
+ return;
+ }
+ this._prefs.removeObserver("", this);
+ this._prefs = null;
+ this.inputField.removeEventListener("underflow", this);
+ this.inputField.removeEventListener("overflow", this);
+ this.inputField.controllers.removeController(this._editItemsController);
+ ]]></destructor>
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body><![CDATA[
+ if (aTopic == "nsPref:changed")
+ this.updatePref(aData);
+ ]]></body>
+ </method>
+
+ <method name="updatePref">
+ <parameter name="aPref"/>
+ <body><![CDATA[
+ if (aPref == "showPopup") {
+ this.showPopup = this._prefs.getBoolPref(aPref);
+ } else if (aPref == "autoFill") {
+ this.autoFill = this._prefs.getBoolPref(aPref);
+ } else if (aPref == "showSearch") {
+ this.minResultsForPopup = this._prefs.getBoolPref(aPref) ? 0 : 1;
+ } else if (aPref == "formatting.enabled") {
+ this._formattingEnabled = this._prefs.getBoolPref(aPref);
+ this._formatValue(this._formattingEnabled && !this.focused);
+ }
+ ]]></body>
+ </method>
+
+ <field name="_overflowing">false</field>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "overflow":
+ this._overflowing = true;
+ break;
+ case "underflow":
+ this._overflowing = false;
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_showTooltip">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var tooltip = aEvent.target;
+ if (this._overflowing)
+ tooltip.label = this.value;
+ else if (this.value)
+ tooltip.label = this.placeholder;
+ else
+ aEvent.preventDefault();
+ ]]></body>
+ </method>
+
+ <field name="_formattingEnabled">true</field>
+
+ <method name="_formatValue">
+ <parameter name="formattingEnabled"/>
+ <body><![CDATA[
+ if (!this.editor)
+ return;
+
+ var controller = this.editor.selectionController;
+ var selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
+ selection.removeAllRanges();
+ if (!formattingEnabled)
+ return;
+
+ var textNode = this.editor.rootElement.firstChild;
+ var value = textNode.textContent;
+
+ var protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
+ if (protocol && !/^https?:|ftp:/.test(protocol[0]))
+ return;
+ var matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
+ if (!matchedURL)
+ return;
+
+ var [, preDomain, domain] = matchedURL;
+ var subDomain = "";
+ // getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
+ if (domain[0] != "[") {
+ try {
+ var baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
+ if (!domain.endsWith(baseDomain)) {
+ // getBaseDomainFromHost converts its resultant to ACE.
+ let IDNService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+ baseDomain = IDNService.convertACEtoUTF8(baseDomain);
+ }
+ if (baseDomain != domain) {
+ subDomain = domain.slice(0, -baseDomain.length);
+ }
+ } catch (e) {}
+ }
+
+ var startLength = preDomain.length + subDomain.length;
+ if (startLength) {
+ var startRange = document.createRange();
+ startRange.setStart(textNode, 0);
+ startRange.setEnd(textNode, startLength);
+ selection.addRange(startRange);
+ }
+
+ var endLength = preDomain.length + domain.length;
+ if (endLength < value.length) {
+ var endRange = document.createRange();
+ endRange.setStart(textNode, endLength);
+ endRange.setEnd(textNode, value.length);
+ selection.addRange(endRange);
+ }
+ ]]></body>
+ </method>
+
+ <method name="autoFillInput">
+ <parameter name="aSessionName"/>
+ <parameter name="aResults"/>
+ <parameter name="aUseFirstMatchIfNoDefault"/>
+ <body><![CDATA[
+ if (this.mInputElt.selectionEnd < this.currentSearchString.length ||
+ this.mDefaultMatchFilled)
+ return;
+
+ if (!this.mFinishAfterSearch && this.autoFill &&
+ this.mLastKeyCode != KeyEvent.DOM_VK_BACK_SPACE &&
+ this.mLastKeyCode != KeyEvent.DOM_VK_DELETE) {
+ var indexToUse = aResults.defaultItemIndex;
+ if (aUseFirstMatchIfNoDefault && indexToUse == -1)
+ indexToUse = 0;
+
+ if (indexToUse != -1) {
+ var result = this.getSessionValueAt(aSessionName, indexToUse);
+ var entry = this.value;
+ var suffix = "";
+ if (/^ftp:\/\/ftp\b/.test(result) &&
+ result.lastIndexOf("ftp://" + entry, 0) == 0)
+ suffix = result.slice(entry.length + 6);
+ else if (!/^http:\/\/ftp\b/.test(result) &&
+ result.lastIndexOf("http://" + entry, 0) == 0)
+ suffix = result.slice(entry.length + 7);
+ else if (result.lastIndexOf(entry, 0) == 0)
+ suffix = result.slice(entry.length);
+
+ if (suffix) {
+ this.setTextValue(this.value + suffix);
+ this.mInputElt.setSelectionRange(entry.length, this.value.length);
+ this.mDefaultMatchFilled = true;
+ }
+ this.mNeedToComplete = true;
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="_getSelectedValueForClipboard">
+ <body>
+ <![CDATA[
+ var inputVal = this.inputField.value;
+ var val = inputVal.substring(this.selectionStart, this.selectionEnd);
+
+ /* If the entire value is selected and it's a valid non-javascript,
+ non-data URI, encode it. */
+ if (val == inputVal &&
+ gProxyButton.getAttribute("pageproxystate") == "valid") {
+ var uri;
+ try {
+ uri = makeURI(val);
+ } catch (e) {}
+
+ if (uri && !uri.schemeIs("javascript") && !uri.schemeIs("data")) {
+ val = uri.spec;
+
+ // Parentheses are known to confuse third-party applications (bug 458565).
+ val = val.replace(/[()]/g, c => escape(c));
+ }
+ }
+
+ return val;
+ ]]>
+ </body>
+ </method>
+
+ <field name="_editItemsController"><![CDATA[
+ ({
+ supportsCommand: function(aCommand) {
+ switch (aCommand) {
+ case "cmd_copy":
+ case "cmd_cut":
+ case "cmd_pasteAndGo":
+ return true;
+ }
+ return false;
+ },
+ isCommandEnabled: function(aCommand) {
+ var hasSelection = this.selectionStart < this.selectionEnd;
+ switch (aCommand) {
+ case "cmd_copy":
+ return hasSelection;
+ case "cmd_cut":
+ return !this.readOnly && hasSelection;
+ case "cmd_pasteAndGo":
+ return document.commandDispatcher
+ .getControllerForCommand("cmd_paste")
+ .isCommandEnabled("cmd_paste");
+ }
+ return false;
+ }.bind(this),
+ doCommand: function(aCommand) {
+ switch (aCommand) {
+ case "cmd_copy":
+ case "cmd_cut":
+ var val = this._getSelectedValueForClipboard();
+ var controller = this._editItemsController;
+ if (!val || !controller.isCommandEnabled(aCommand))
+ return;
+
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyString(val);
+
+ if (aCommand == "cmd_cut")
+ goDoCommand("cmd_delete");
+ break;
+
+ case "cmd_pasteAndGo":
+ this.select();
+ goDoCommand("cmd_paste");
+ this._fireEvent("textentered", "pasting");
+ break;
+ }
+ }.bind(this),
+ onEvent: function(aEventName) {}
+ })
+ ]]></field>
+ </implementation>
+
+ <handlers>
+ <handler event="keypress"
+ key="&locationBar.accesskey;"
+ modifiers="access"
+ action="this.select();"/>
+
+ <handler event="ValueChange"><![CDATA[
+ if (this._formattingEnabled && !this.focused)
+ this._formatValue(true);
+ ]]></handler>
+
+ <handler event="blur"><![CDATA[
+ if (this._formattingEnabled)
+ this._formatValue(true);
+ ]]></handler>
+
+ <handler event="focus"><![CDATA[
+ this._formatValue(false);
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="autocomplete-result-popup" extends="chrome://global/content/autocomplete.xml#autocomplete-result-popup">
+ <content>
+ <xul:tree anonid="tree" class="autocomplete-tree plain" flex="1">
+ <xul:treecols anonid="treecols">
+ <xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteValue" flex="2"/>
+ <xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteComment" flex="1" hidden="true"/>
+ </xul:treecols>
+ <xul:treechildren anonid="treebody" class="autocomplete-treebody" flex="1"/>
+ </xul:tree>
+ <xul:box role="search-box" class="autocomplete-search-box"/>
+ </content>
+
+ <implementation implements="nsIObserver, nsIBrowserSearchInitObserver">
+ <constructor><![CDATA[
+ // listen for changes to default search engine
+ Services.prefs.addObserver("browser.search", this);
+ Services.prefs.addObserver("browser.urlbar", this);
+ Services.obs.addObserver(this, "browser-search-engine-modified");
+ this._initialized = true;
+ Services.search.init(this);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ Services.prefs.removeObserver("browser.search", this);
+ Services.prefs.removeObserver("browser.urlbar", this);
+ if (this._initialized) {
+ this._initialized = false;
+ Services.obs.removeObserver(this, "browser-search-engine-modified");
+ }
+ ]]></destructor>
+
+ <property name="showSearch" onget="return this.mShowSearch;">
+ <setter><![CDATA[
+ this.mShowSearch = val;
+ if (val) {
+ this.updateEngines();
+ this.mSearchBox.removeAttribute("hidden");
+ } else {
+ this.clearEngines();
+ this.mSearchBox.setAttribute("hidden", "true");
+ }
+ ]]></setter>
+ </property>
+
+ <property name="defaultSearchEngine"
+ onget="return this.textbox.getAttribute('defaultSearchEngine') == 'true';"
+ onset="this.textbox.setAttribute('defaultSearchEngine', val); return val;"/>
+
+ <field name="mSearchBox">
+ document.getAnonymousElementByAttribute(this, "role", "search-box");
+ </field>
+
+ <field name="mInputListener"><![CDATA[
+ (function(aEvent) {
+ // "this" is the textbox, not the popup
+ if (this.mSearchInputTO)
+ window.clearTimeout(this.mSearchInputTO);
+ this.mSearchInputTO = window.setTimeout(this.popup.mInputTimeout, this.timeout, this);
+ });
+ ]]></field>
+
+ <field name="mInputTimeout"><![CDATA[
+ (function(me) {
+ me.popup.mSearchBox.searchValue = me.value;
+ me.mSearchInputTO = 0;
+ });
+ ]]></field>
+
+ <field name="_initialized">false</field>
+ <field name="mEnginesReady">false</field>
+
+ <property name="overrideValue" readonly="true">
+ <getter><![CDATA[
+ if (this.mSearchBox.selectedIndex != -1) {
+ return this.mSearchBox.overrideValue;
+ }
+ return null;
+ ]]></getter>
+ </property>
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body><![CDATA[
+ switch (aTopic) {
+ case "browser-search-engine-modified":
+ if (aData == "engine-current") {
+ this.updateEngines();
+ }
+ break;
+ case "nsPref:changed":
+ if (/^browser\.search\./.test(aData))
+ Services.search.init(this);
+ else if (aData == "browser.urlbar.showSearch")
+ this.updateShowSearch();
+ break;
+ default:
+ }
+ ]]></body>
+ </method>
+
+ <method name="updateShowSearch">
+ <body><![CDATA[
+ this.showSearch = Services.prefs.getBoolPref("browser.urlbar.showSearch");
+ ]]></body>
+ </method>
+
+ <method name="onInitComplete">
+ <parameter name="aStatus"/>
+ <body><![CDATA[
+ if (!this._initialized)
+ return;
+ if (Components.isSuccessCode(aStatus)) {
+ // Refresh the engines.
+ this.updateEngines();
+ } else {
+ Cu.reportError("Cannot initialize search service, bailing out: " + aStatus);
+ }
+ ]]></body>
+ </method>
+
+ <method name="addEngine">
+ <parameter name="aName"/>
+ <parameter name="aIcon"/>
+ <parameter name="aSearchBarUrl"/>
+ <body><![CDATA[
+ var box = document.createElement("box");
+ box.setAttribute("class", "autocomplete-search-engine");
+ box.setAttribute("name", aName);
+ if (aIcon)
+ box.setAttribute("icon", aIcon.spec);
+ box.setAttribute("searchBarUrl", aSearchBarUrl);
+ box.setAttribute("engineIndex", this.childNodes.length);
+ this.mSearchBox.appendChild(box);
+ ]]></body>
+ </method>
+
+ <method name="clearEngines">
+ <body><![CDATA[
+ while (this.mSearchBox.hasChildNodes())
+ this.mSearchBox.lastChild.remove();
+ ]]></body>
+ </method>
+
+ <method name="updateEngines">
+ <body><![CDATA[
+ var defaultEngine = Services.search.defaultEngine;
+ if (defaultEngine) {
+ this.clearEngines();
+ this.addEngine(defaultEngine.name, defaultEngine.iconURI,
+ defaultEngine.searchForm);
+ }
+
+ this.mEnginesReady = true;
+ ]]></body>
+ </method>
+
+ <method name="clearSelection">
+ <body>
+ this.view.selection.clearSelection();
+ this.mSearchBox.selectedIndex = -1;
+ </body>
+ </method>
+
+ <method name="selectBy">
+ <parameter name="aReverse"/>
+ <parameter name="aPage"/>
+ <body><![CDATA[
+ var sel;
+ if (this.selectedIndex == -1 && aReverse ||
+ this.mSearchBox.selectedIndex != -1) {
+ sel = this.mSearchBox.selectBy(aReverse, aPage);
+ if (sel != -1 || !aReverse)
+ return -1;
+ }
+
+ sel = this.getNextIndex(aReverse, aPage, this.selectedIndex, this.view.rowCount - 1);
+ this.selectedIndex = sel;
+ if (sel == -1 && !aReverse)
+ this.mSearchBox.selectBy(aReverse, aPage);
+ else if (this.mSearchBox.selectedIndex != -1)
+ this.mSearchBox.selectedIndex = -1;
+ return sel;
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="popupshowing"><![CDATA[
+ // sync up with prefs about showing search in the URL bar if
+ // the URL bar hasn't finished initializing the default engine info
+ // -or-
+ // the search sidebar tab was displayed already triggering a change
+ // notification to the browser.search pref branch listener here which
+ // calls updateEngines but doesn't cause the ``Search for ...''
+ // display string to be updated
+ if ( (!this.mEnginesReady && this.defaultSearchEngine) ||
+ !("mShowSearch" in this) )
+ this.updateShowSearch();
+
+ if (this.mShowSearch) {
+ this.textbox.mSearchInputTO = 0;
+ this.textbox.addEventListener("input", this.mInputListener);
+ if ("searchValue" in this.mSearchBox)
+ this.mSearchBox.searchValue = this.textbox.currentSearchString;
+ else
+ this.mSearchBox.setAttribute("searchvalue", this.textbox.currentSearchString);
+ }
+ ]]></handler>
+
+ <handler event="popuphiding"><![CDATA[
+ if (this.mShowSearch)
+ this.textbox.removeEventListener("input", this.mInputListener);
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="autocomplete-search-box">
+ <content orient="vertical"/>
+
+ <implementation>
+ <constructor><![CDATA[
+ this.navigatorBundle = Services.strings
+ .createBundle("chrome://navigator/locale/navigator.properties");
+
+ var text = this.getAttribute("searchvalue");
+ if (text)
+ this.searchValue = text;
+ ]]></constructor>
+
+ <field name="mSelectedIndex">
+ -1
+ </field>
+
+ <property name="activeChild"
+ onget="return this.childNodes[this.mSelectedIndex]"/>
+
+ <property name="selectedIndex">
+ <getter>return this.mSelectedIndex;</getter>
+
+ <setter><![CDATA[
+ if (this.mSelectedIndex != -1)
+ this.activeChild.removeAttribute("menuactive");
+
+ this.mSelectedIndex = val;
+
+ if (val != -1)
+ this.activeChild.setAttribute("menuactive", "true");
+ return val;
+ ]]></setter>
+
+ </property>
+
+ <property name="searchValue">
+ <getter><![CDATA[
+ return this.mSearchValue;
+ ]]></getter>
+ <setter><![CDATA[
+ this.mSearchValue = val;
+
+ const kids = this.childNodes;
+ for (var i = 0; i < kids.length; ++i) {
+ kids[i].setAttribute("label",
+ this.navigatorBundle.formatStringFromName(
+ "searchFor", [kids[i].getAttribute("name"), val], 2));
+ }
+ ]]></setter>
+ </property>
+
+ <method name="selectBy">
+ <parameter name="aReverse"/>
+ <parameter name="aPage"/>
+ <body><![CDATA[
+ return this.selectedIndex = this.parentNode.getNextIndex(aReverse, aPage, this.selectedIndex, this.childNodes.length - 1);
+ ]]></body>
+ </method>
+
+ <property name="overrideValue" readonly="true">
+ <getter><![CDATA[
+ var item = this.activeChild;
+ if (item) {
+ // XXXsearch: using default engine for now, this ignores the following values:
+ // item.getAttribute("searchEngine") and item.getAttribute("searchBarUrl")
+ var engine = Services.search.defaultEngine;
+
+ var submission = engine.getSubmission(this.mSearchValue); // HTML response
+
+ // getSubmission can return null if the engine doesn't have a URL
+ // with a text/html response type. This is unlikely (since
+ // SearchService._addEngineToStore() should fail for such an engine),
+ // but let's be on the safe side.
+ if (!submission)
+ return null;
+
+ // XXXsearch: the submission object may have postData but .overrideValue can't deal with that
+ // see http://mxr.mozilla.org/comm-central/source/mozilla/netwerk/base/public/nsIBrowserSearchService.idl#47
+ // we need to figure out what to do with that case here
+
+ return submission.uri.spec;
+ }
+ return null;
+ ]]></getter>
+ </property>
+
+ </implementation>
+
+ <handlers>
+ <handler event="mouseup">
+ this.parentNode.textbox.onResultClick();
+ </handler>
+ </handlers>
+
+ </binding>
+
+ <binding id="autocomplete-search-engine">
+ <content>
+ <xul:image class="autocomplete-search-engine-img" xbl:inherits="src=icon"/>
+ <xul:label class="autocomplete-search-engine-text" xbl:inherits="value=label" crop="right" flex="1"/>
+ </content>
+
+ <handlers>
+ <handler event="mousemove">
+ this.parentNode.selectedIndex = Number(this.getAttribute("engineIndex"));
+ </handler>
+
+ <handler event="mouseout">
+ this.parentNode.selectedIndex = -1;
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="input-box-paste" extends="chrome://global/content/bindings/textbox.xml#input-box">
+ <content context="_child">
+ <children/>
+ <xul:menupopup anonid="input-box-contextmenu"
+ class="textbox-contextmenu"
+ onpopupshowing="var input =
+ this.parentNode.getElementsByAttribute('anonid', 'input')[0];
+ if (document.commandDispatcher.focusedElement != input)
+ input.focus();
+ this.parentNode._doPopupItemEnabling(this);"
+ oncommand="var cmd = event.originalTarget.getAttribute('cmd'); if(cmd) { this.parentNode.doCommand(cmd); event.stopPropagation(); }">
+ <xul:menuitem label="&undoCmd.label;" accesskey="&undoCmd.accesskey;" cmd="cmd_undo"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" cmd="cmd_cut"/>
+ <xul:menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" cmd="cmd_copy"/>
+ <xul:menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" cmd="cmd_paste"/>
+ <xul:menuitem label="&pasteGoCmd.label;" accesskey="&pasteGoCmd.accesskey;" cmd="cmd_pasteAndGo"/>
+ <xul:menuitem label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;" cmd="cmd_delete"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;" cmd="cmd_selectAll"/>
+ </xul:menupopup>
+ </content>
+ </binding>
+
+</bindings>
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 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://devtools/skin/devtools-browser.css" type="text/css"?>
+<?xml-stylesheet href="chrome://navigator/skin/webDeveloper.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://navigator/locale/webDeveloper.dtd">
+
+<overlay id="webDeveloperOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://navigator/content/webDeveloperOverlay.js"/>
+
+ <menupopup id="toolsPopup">
+ <menuitem id="validatePage"
+ label="&validatePage.label;"
+ accesskey="&validatePage.accesskey;"
+ oncommand="gWebDeveloper.validateThisPage(); event.stopPropagation();"/>
+ <menuseparator id="devToolsStartSeparator"/>
+ <menuitem id="menu_devToolbox"
+ observes="devtoolsMenuBroadcaster_DevToolbox"
+ label="&devToolboxMenuItem.label;"
+ accesskey="&devToolboxMenuItem.accesskey;"/>
+ <menuseparator id="menu_devtools_separator"/>
+ <menuitem id="menu_devToolbar"
+ observes="devtoolsMenuBroadcaster_DevToolbar"
+ type="checkbox" autocheck="false"
+ label="&devToolbarMenu.label;"
+ accesskey="&devToolbarMenu.accesskey;"/>
+ <menuitem id="menu_browserToolbox"
+ observes="devtoolsMenuBroadcaster_BrowserToolbox"
+ label="&browserToolboxMenu.label;"
+ accesskey="&browserToolboxMenu.accesskey;"/>
+ <menuitem id="menu_browserContentToolbox"
+ observes="devtoolsMenuBroadcaster_BrowserContentToolbox"
+ label="&browserContentToolboxMenu.label;"
+ accesskey="&browserContentToolboxMenu.accesskey;" />
+ <menuitem id="menu_browserConsole"
+ observes="devtoolsMenuBroadcaster_BrowserConsole"
+ label="&browserConsoleCmd.label;"
+ accesskey="&browserConsoleCmd.accesskey;"/>
+ <menuitem id="menu_responsiveUI"
+ observes="devtoolsMenuBroadcaster_ResponsiveUI"
+ type="checkbox" autocheck="false"
+ label="&responsiveDesignTool.label;"
+ accesskey="&responsiveDesignTool.accesskey;"/>
+ <menuitem id="menu_eyedropper"
+ observes="devtoolsMenuBroadcaster_Eyedropper"
+ type="checkbox" autocheck="false"
+ label="&eyedropper.label;"
+ accesskey="&eyedropper.accesskey;"/>
+ <menuitem id="menu_scratchpad"
+ observes="devtoolsMenuBroadcaster_Scratchpad"
+ label="&scratchpad.label;"
+ accesskey="&scratchpad.accesskey;"/>
+ <menuitem id="devtoolsDebugger"
+ type="checkbox"
+ label="&allowRemoteDebugging.label;"
+ accesskey="&allowRemoteDebugging.accesskey;"
+ oncommand="gWebDeveloper.enableDebugger(this);"/>
+ <menuitem id="menu_devtools_connect"
+ observes="devtoolsMenuBroadcaster_connect"
+ label="&devtoolsConnect.label;"
+ accesskey="&devtoolsConnect.accesskey;"/>
+ <menuseparator id="devToolsEndSeparator"/>
+ <menuitem id="getMoreDevtools"
+ observes="devtoolsMenuBroadcaster_GetMoreTools"
+ label="&getMoreDevtoolsCmd.label;"
+ accesskey="&getMoreDevtoolsCmd.accesskey;"/>
+ </menupopup>
+
+ <commandset id="mainCommandSet">
+ <command id="Tools:DevToolbox" oncommand="gDevToolsBrowser.toggleToolboxCommand(gBrowser);"/>
+ <command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
+ <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
+ <command id="Tools:BrowserToolbox" oncommand="BrowserToolboxProcess.init();" disabled="true" hidden="true"/>
+ <command id="Tools:BrowserContentToolbox" oncommand="gDevToolsBrowser.openContentProcessToolbox();" disabled="true" hidden="true"/>
+ <command id="Tools:BrowserConsole" oncommand="HUDService.openBrowserConsoleOrFocus();"/>
+ <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();"/>
+ <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();"/>
+ <command id="Tools:Eyedropper" oncommand="openEyedropper();"/>
+ <command id="Tools:DevToolsConnect" oncommand="gDevToolsBrowser.openConnectScreen(gBrowser)" disabled="true" hidden="true"/>
+ </commandset>
+
+ <keyset id="mainKeyset">
+ <key id="key_devToolboxMenuItemF12" keycode="&devToolsCmd.keycode;" keytext="&devToolsCmd.keytext;"
+ command="Tools:DevToolbox"/>
+ <key id="key_browserConsole" key="&browserConsoleCmd.commandkey;" modifiers="accel,shift"
+ command="Tools:BrowserConsole"/>
+ <key id="key_browserToolbox" key="&browserToolboxCmd.commandkey;" modifiers="accel,alt,shift"
+ command="Tools:BrowserToolbox"/>
+ <key id="key_devToolbar" keycode="&devToolbar.keycode;" modifiers="shift" keytext="&devToolbar.keytext;"
+ command="Tools:DevToolbarFocus"/>
+ <key id="key_devToolboxMenuItem" keytext="&devToolboxMenuItem.keytext;" key="&devToolboxMenuItem.keytext;" modifiers="accel,shift"
+ command="Tools:DevToolbox"/>
+ <key id="key_scratchpad" keycode="&scratchpad.keycode;" modifiers="shift" keytext="&scratchpad.keytext;"
+ command="Tools:Scratchpad"/>
+ </keyset>
+
+ <broadcasterset id="mainBroadcasterSet">
+ <!-- DevTools broadcasters -->
+ <broadcaster id="devtoolsMenuBroadcaster_DevToolbox"
+ type="checkbox" autocheck="false"
+ command="Tools:DevToolbox"
+ key="key_devToolboxMenuItem"/>
+ <broadcaster id="devtoolsMenuBroadcaster_DevToolbar"
+ command="Tools:DevToolbar"
+ key="key_devToolbar"/>
+ <broadcaster id="devtoolsMenuBroadcaster_BrowserToolbox"
+ key="key_browserToolbox"
+ command="Tools:BrowserToolbox"/>
+ <broadcaster id="devtoolsMenuBroadcaster_BrowserContentToolbox"
+ command="Tools:BrowserContentToolbox"/>
+ <broadcaster id="devtoolsMenuBroadcaster_BrowserConsole"
+ key="key_browserConsole"
+ command="Tools:BrowserConsole"/>
+ <broadcaster id="devtoolsMenuBroadcaster_Scratchpad"
+ command="Tools:Scratchpad"
+ key="key_scratchpad"/>
+ <broadcaster id="devtoolsMenuBroadcaster_ResponsiveUI"
+ command="Tools:ResponsiveUI"/>
+ <broadcaster id="devtoolsMenuBroadcaster_Eyedropper"
+ command="Tools:Eyedropper"/>
+ <broadcaster id="devtoolsMenuBroadcaster_PageSource"
+ key="key_viewSource"
+ command="View:PageSource"/>
+ <broadcaster id="devtoolsMenuBroadcaster_GetMoreTools"
+ oncommand="openUILinkIn('https://addons.mozilla.org/firefox/collections/mozilla/webdeveloper/', 'tab');"/>
+ <broadcaster id="devtoolsMenuBroadcaster_connect"
+ command="Tools:DevToolsConnect"/>
+ </broadcasterset>
+
+ <window id="main-window">
+ <toolbar id="developer-toolbar" xpfe="false" hidden="true"
+ insertbefore="status-bar">
+ <observes element="main-window" attribute="devtoolstheme"/>
+ <stack class="gclitoolbar-stack-node" flex="1">
+ <textbox class="gclitoolbar-input-node" rows="1"/>
+ <hbox class="gclitoolbar-complete-node"/>
+ </stack>
+ <toolbarbutton id="developer-toolbar-toolbox-button"
+ class="developer-toolbar-button"
+ label="&devToolboxMenuItem.label;"
+ observes="devtoolsMenuBroadcaster_DevToolbox"
+ tooltiptext="&devToolbarToolsButton.tooltip;"
+ _defaultTooltipText="&devToolbarToolsButton.tooltip;"/>
+
+ <toolbarbutton id="developer-toolbar-closebutton"
+ class="close-icon"
+ oncommand="DeveloperToolbar.hide();"
+ tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
+ </toolbar>
+ </window>
+</overlay>