/* -*- 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/. */ let Cc = Components.classes; let Ci = Components.interfaces; let Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler", "resource:///modules/ContentLinkHandler.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector", "resource:///modules/translation/LanguageDetector.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent", "resource://gre/modules/LoginManagerContent.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils", "resource://gre/modules/InsecurePasswordUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "UITour", "resource:///modules/UITour.jsm"); // Creates a new nsIURI object. function makeURI(uri, originCharset, baseURI) { return Services.io.newURI(uri, originCharset, baseURI); } addMessageListener("Browser:HideSessionRestoreButton", function (message) { // Hide session restore button on about:home let doc = content.document; let container; if (doc.documentURI.toLowerCase() == "about:home" && (container = doc.getElementById("sessionRestoreContainer"))){ container.hidden = true; } }); if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) { addEventListener("contextmenu", function (event) { sendAsyncMessage("contextmenu", {}, { event: event }); }, false); } else { addEventListener("DOMFormHasPassword", function(event) { InsecurePasswordUtils.checkForInsecurePasswords(event.target); LoginManagerContent.onFormPassword(event); }); addEventListener("DOMAutoComplete", function(event) { LoginManagerContent.onUsernameInput(event); }); addEventListener("blur", function(event) { LoginManagerContent.onUsernameInput(event); }); addEventListener("mozUITour", function(event) { if (!Services.prefs.getBoolPref("browser.uitour.enabled")) return; let handled = UITour.onPageEvent(event); if (handled) addEventListener("pagehide", UITour); }, false, true); } let AboutHomeListener = { init: function(chromeGlobal) { chromeGlobal.addEventListener('AboutHomeLoad', () => this.onPageLoad(), false, true); }, handleEvent: function(aEvent) { switch (aEvent.type) { case "AboutHomeLoad": this.onPageLoad(); break; } }, receiveMessage: function(aMessage) { switch (aMessage.name) { case "AboutHome:Update": this.onUpdate(aMessage.data); break; } }, onUpdate: function(aData) { let doc = content.document; if (doc.documentURI.toLowerCase() != "about:home") return; if (aData.showRestoreLastSession && !PrivateBrowsingUtils.isWindowPrivate(content)) doc.getElementById("launcher").setAttribute("session", "true"); // Inject search engine and snippets URL. let docElt = doc.documentElement; // set the following attributes BEFORE searchEngineName, which triggers to // show the snippets when it's set. docElt.setAttribute("snippetsURL", aData.snippetsURL); if (aData.showKnowYourRights) docElt.setAttribute("showKnowYourRights", "true"); docElt.setAttribute("snippetsVersion", aData.snippetsVersion); docElt.setAttribute("searchEngineName", aData.defaultEngineName); }, onPageLoad: function() { let doc = content.document; if (doc.documentURI.toLowerCase() != "about:home" || doc.documentElement.hasAttribute("hasBrowserHandlers")) { return; } doc.documentElement.setAttribute("hasBrowserHandlers", "true"); let self = this; addMessageListener("AboutHome:Update", self); addEventListener("click", this.onClick, true); addEventListener("pagehide", function onPageHide(event) { if (event.target.defaultView.frameElement) return; removeMessageListener("AboutHome:Update", self); removeEventListener("click", self.onClick, true); removeEventListener("pagehide", onPageHide, true); if (event.target.documentElement) event.target.documentElement.removeAttribute("hasBrowserHandlers"); }, true); // XXX bug 738646 - when Marketplace is launched, remove this statement and // the hidden attribute set on the apps button in aboutHome.xhtml if (Services.prefs.getPrefType("browser.aboutHome.apps") == Services.prefs.PREF_BOOL && Services.prefs.getBoolPref("browser.aboutHome.apps")) doc.getElementById("apps").removeAttribute("hidden"); sendAsyncMessage("AboutHome:RequestUpdate"); doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) { sendAsyncMessage("AboutHome:Search", { searchData: e.detail }); }, true, true); }, onClick: function(aEvent) { if (!aEvent.isTrusted || // Don't trust synthetic events aEvent.button == 2 || aEvent.target.localName != "button") { return; } let originalTarget = aEvent.originalTarget; let ownerDoc = originalTarget.ownerDocument; if (ownerDoc.documentURI != "about:home") { // This shouldn't happen, but we're being defensive. return; } let elmId = originalTarget.getAttribute("id"); switch (elmId) { case "restorePreviousSession": sendAsyncMessage("AboutHome:RestorePreviousSession"); ownerDoc.getElementById("launcher").removeAttribute("session"); break; case "downloads": sendAsyncMessage("AboutHome:Downloads"); break; case "bookmarks": sendAsyncMessage("AboutHome:Bookmarks"); break; case "history": sendAsyncMessage("AboutHome:History"); break; case "apps": sendAsyncMessage("AboutHome:Apps"); break; case "addons": sendAsyncMessage("AboutHome:Addons"); break; case "sync": sendAsyncMessage("AboutHome:Sync"); break; case "settings": sendAsyncMessage("AboutHome:Settings"); break; } }, }; AboutHomeListener.init(this); let ContentSearchMediator = { whitelist: new Set([ "about:newtab", ]), init: function (chromeGlobal) { chromeGlobal.addEventListener("ContentSearchClient", this, true, true); addMessageListener("ContentSearch", this); }, handleEvent: function (event) { if (this._contentWhitelisted) { this._sendMsg(event.detail.type, event.detail.data); } }, receiveMessage: function (msg) { if (msg.data.type == "AddToWhitelist") { for (let uri of msg.data.data) { this.whitelist.add(uri); } this._sendMsg("AddToWhitelistAck"); return; } if (this._contentWhitelisted) { this._fireEvent(msg.data.type, msg.data.data); } }, get _contentWhitelisted() { return this.whitelist.has(content.document.documentURI.toLowerCase()); }, _sendMsg: function (type, data=null) { sendAsyncMessage("ContentSearch", { type: type, data: data, }); }, _fireEvent: function (type, data=null) { content.dispatchEvent(new content.CustomEvent("ContentSearchService", { detail: { type: type, data: data, }, })); }, }; ContentSearchMediator.init(this); var global = this; // Lazily load the finder code addMessageListener("Finder:Initialize", function () { let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {}); new RemoteFinderListener(global); }); let ClickEventHandler = { init: function init() { Cc["@mozilla.org/eventlistenerservice;1"] .getService(Ci.nsIEventListenerService) .addSystemEventListener(global, "click", this, true); }, handleEvent: function(event) { // Bug 903016: Most of this code is an unfortunate duplication from // contentAreaClick in browser.js. if (!event.isTrusted || event.defaultPrevented || event.button == 2) return; let [href, node] = this._hrefAndLinkNodeForClickEvent(event); let json = { button: event.button, shiftKey: event.shiftKey, ctrlKey: event.ctrlKey, metaKey: event.metaKey, altKey: event.altKey, href: null, title: null, bookmark: false }; if (href) { json.href = href; if (node) { json.title = node.getAttribute("title"); if (event.button == 0 && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) { json.bookmark = node.getAttribute("rel") == "sidebar"; if (json.bookmark) event.preventDefault(); // Need to prevent the pageload. } } sendAsyncMessage("Content:Click", json); return; } // This might be middle mouse navigation. if (event.button == 1) sendAsyncMessage("Content:Click", json); }, /** * Extracts linkNode and href for the current click target. * * @param event * The click event. * @return [href, linkNode]. * * @note linkNode will be null if the click wasn't on an anchor * element (or XLink). */ _hrefAndLinkNodeForClickEvent: function(event) { function isHTMLLink(aNode) { // Be consistent with what nsContextMenu.js does. return ((aNode instanceof content.HTMLAnchorElement && aNode.href) || (aNode instanceof content.HTMLAreaElement && aNode.href) || aNode instanceof content.HTMLLinkElement); } let node = event.target; while (node && !isHTMLLink(node)) { node = node.parentNode; } if (node) return [node.href, node]; // If there is no linkNode, try simple XLink. let href, baseURI; node = event.target; while (node && !href) { if (node.nodeType == content.Node.ELEMENT_NODE) { href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href"); if (href) baseURI = node.ownerDocument.baseURIObject; } node = node.parentNode; } // In case of XLink, we don't return the node we got href from since // callers expect -like elements. // Note: makeURI() will throw if aUri is not a valid URI. return [href ? makeURI(href, null, baseURI).spec : null, null]; } }; ClickEventHandler.init(); ContentLinkHandler.init(this); addEventListener("DOMWebNotificationClicked", function(event) { sendAsyncMessage("DOMWebNotificationClicked", {}); }, false); let PageStyleHandler = { init: function() { addMessageListener("PageStyle:Switch", this); addMessageListener("PageStyle:Disable", this); // Send a CPOW to the parent so that it can synchronously request // the list of style sheets. sendSyncMessage("PageStyle:SetSyncHandler", {}, {syncHandler: this}); }, get markupDocumentViewer() { return docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer); }, // Called synchronously via CPOW from the parent. getStyleSheetInfo: function() { let styleSheets = this._filterStyleSheets(this.getAllStyleSheets()); return { styleSheets: styleSheets, authorStyleDisabled: this.markupDocumentViewer.authorStyleDisabled, preferredStyleSheetSet: content.document.preferredStyleSheetSet }; }, // Called synchronously via CPOW from the parent. getAllStyleSheets: function(frameset = content) { let selfSheets = Array.slice(frameset.document.styleSheets); let subSheets = Array.map(frameset.frames, frame => this.getAllStyleSheets(frame)); return selfSheets.concat(...subSheets); }, receiveMessage: function(msg) { switch (msg.name) { case "PageStyle:Switch": this.markupDocumentViewer.authorStyleDisabled = false; this._stylesheetSwitchAll(content, msg.data.title); break; case "PageStyle:Disable": this.markupDocumentViewer.authorStyleDisabled = true; break; } }, _stylesheetSwitchAll: function (frameset, title) { if (!title || this._stylesheetInFrame(frameset, title)) { this._stylesheetSwitchFrame(frameset, title); } for (let i = 0; i < frameset.frames.length; i++) { // Recurse into sub-frames. this._stylesheetSwitchAll(frameset.frames[i], title); } }, _stylesheetSwitchFrame: function (frame, title) { var docStyleSheets = frame.document.styleSheets; for (let i = 0; i < docStyleSheets.length; ++i) { let docStyleSheet = docStyleSheets[i]; if (docStyleSheet.title) { docStyleSheet.disabled = (docStyleSheet.title != title); } else if (docStyleSheet.disabled) { docStyleSheet.disabled = false; } } }, _stylesheetInFrame: function (frame, title) { return Array.some(frame.document.styleSheets, (styleSheet) => styleSheet.title == title); }, _filterStyleSheets: function(styleSheets) { let result = []; for (let currentStyleSheet of styleSheets) { if (!currentStyleSheet.title) continue; // Skip any stylesheets that don't match the screen media type. if (currentStyleSheet.media.length > 0) { let mediaQueryList = currentStyleSheet.media.mediaText; if (!content.matchMedia(mediaQueryList).matches) { continue; } } result.push({title: currentStyleSheet.title, disabled: currentStyleSheet.disabled}); } return result; }, }; PageStyleHandler.init(); let TranslationHandler = { init: function() { let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebProgress); webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT); }, /* nsIWebProgressListener implementation */ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) { if (!aWebProgress.isTopLevel || !(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP)) return; let url = aRequest.name; if (!url.startsWith("http://") && !url.startsWith("https://")) return; // Grab a 60k sample of text from the page. let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"] .createInstance(Ci.nsIDocumentEncoder); encoder.init(content.document, "text/plain", encoder.SkipInvisibleContent); let string = encoder.encodeToStringWithMaxLength(60 * 1024); // Language detection isn't reliable on very short strings. if (string.length < 100) return; LanguageDetector.detectLanguage(string).then(result => { if (result.confident) sendAsyncMessage("LanguageDetection:Result", result.language); }); }, // Unused methods. onProgressChange: function() {}, onLocationChange: function() {}, onStatusChange: function() {}, onSecurityChange: function() {}, QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]) }; if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) TranslationHandler.init();