1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/base/content/content.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,491 @@ 1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +let Cc = Components.classes; 1.10 +let Ci = Components.interfaces; 1.11 +let Cu = Components.utils; 1.12 + 1.13 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.14 +Cu.import("resource://gre/modules/Services.jsm"); 1.15 + 1.16 +XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler", 1.17 + "resource:///modules/ContentLinkHandler.jsm"); 1.18 +XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector", 1.19 + "resource:///modules/translation/LanguageDetector.jsm"); 1.20 +XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent", 1.21 + "resource://gre/modules/LoginManagerContent.jsm"); 1.22 +XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils", 1.23 + "resource://gre/modules/InsecurePasswordUtils.jsm"); 1.24 +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", 1.25 + "resource://gre/modules/PrivateBrowsingUtils.jsm"); 1.26 +XPCOMUtils.defineLazyModuleGetter(this, "UITour", 1.27 + "resource:///modules/UITour.jsm"); 1.28 + 1.29 +// Creates a new nsIURI object. 1.30 +function makeURI(uri, originCharset, baseURI) { 1.31 + return Services.io.newURI(uri, originCharset, baseURI); 1.32 +} 1.33 + 1.34 +addMessageListener("Browser:HideSessionRestoreButton", function (message) { 1.35 + // Hide session restore button on about:home 1.36 + let doc = content.document; 1.37 + let container; 1.38 + if (doc.documentURI.toLowerCase() == "about:home" && 1.39 + (container = doc.getElementById("sessionRestoreContainer"))){ 1.40 + container.hidden = true; 1.41 + } 1.42 +}); 1.43 + 1.44 +if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) { 1.45 + addEventListener("contextmenu", function (event) { 1.46 + sendAsyncMessage("contextmenu", {}, { event: event }); 1.47 + }, false); 1.48 +} else { 1.49 + addEventListener("DOMFormHasPassword", function(event) { 1.50 + InsecurePasswordUtils.checkForInsecurePasswords(event.target); 1.51 + LoginManagerContent.onFormPassword(event); 1.52 + }); 1.53 + addEventListener("DOMAutoComplete", function(event) { 1.54 + LoginManagerContent.onUsernameInput(event); 1.55 + }); 1.56 + addEventListener("blur", function(event) { 1.57 + LoginManagerContent.onUsernameInput(event); 1.58 + }); 1.59 + 1.60 + addEventListener("mozUITour", function(event) { 1.61 + if (!Services.prefs.getBoolPref("browser.uitour.enabled")) 1.62 + return; 1.63 + 1.64 + let handled = UITour.onPageEvent(event); 1.65 + if (handled) 1.66 + addEventListener("pagehide", UITour); 1.67 + }, false, true); 1.68 +} 1.69 + 1.70 +let AboutHomeListener = { 1.71 + init: function(chromeGlobal) { 1.72 + chromeGlobal.addEventListener('AboutHomeLoad', () => this.onPageLoad(), false, true); 1.73 + }, 1.74 + 1.75 + handleEvent: function(aEvent) { 1.76 + switch (aEvent.type) { 1.77 + case "AboutHomeLoad": 1.78 + this.onPageLoad(); 1.79 + break; 1.80 + } 1.81 + }, 1.82 + 1.83 + receiveMessage: function(aMessage) { 1.84 + switch (aMessage.name) { 1.85 + case "AboutHome:Update": 1.86 + this.onUpdate(aMessage.data); 1.87 + break; 1.88 + } 1.89 + }, 1.90 + 1.91 + onUpdate: function(aData) { 1.92 + let doc = content.document; 1.93 + if (doc.documentURI.toLowerCase() != "about:home") 1.94 + return; 1.95 + 1.96 + if (aData.showRestoreLastSession && !PrivateBrowsingUtils.isWindowPrivate(content)) 1.97 + doc.getElementById("launcher").setAttribute("session", "true"); 1.98 + 1.99 + // Inject search engine and snippets URL. 1.100 + let docElt = doc.documentElement; 1.101 + // set the following attributes BEFORE searchEngineName, which triggers to 1.102 + // show the snippets when it's set. 1.103 + docElt.setAttribute("snippetsURL", aData.snippetsURL); 1.104 + if (aData.showKnowYourRights) 1.105 + docElt.setAttribute("showKnowYourRights", "true"); 1.106 + docElt.setAttribute("snippetsVersion", aData.snippetsVersion); 1.107 + docElt.setAttribute("searchEngineName", aData.defaultEngineName); 1.108 + }, 1.109 + 1.110 + onPageLoad: function() { 1.111 + let doc = content.document; 1.112 + if (doc.documentURI.toLowerCase() != "about:home" || 1.113 + doc.documentElement.hasAttribute("hasBrowserHandlers")) { 1.114 + return; 1.115 + } 1.116 + 1.117 + doc.documentElement.setAttribute("hasBrowserHandlers", "true"); 1.118 + let self = this; 1.119 + addMessageListener("AboutHome:Update", self); 1.120 + addEventListener("click", this.onClick, true); 1.121 + addEventListener("pagehide", function onPageHide(event) { 1.122 + if (event.target.defaultView.frameElement) 1.123 + return; 1.124 + removeMessageListener("AboutHome:Update", self); 1.125 + removeEventListener("click", self.onClick, true); 1.126 + removeEventListener("pagehide", onPageHide, true); 1.127 + if (event.target.documentElement) 1.128 + event.target.documentElement.removeAttribute("hasBrowserHandlers"); 1.129 + }, true); 1.130 + 1.131 + // XXX bug 738646 - when Marketplace is launched, remove this statement and 1.132 + // the hidden attribute set on the apps button in aboutHome.xhtml 1.133 + if (Services.prefs.getPrefType("browser.aboutHome.apps") == Services.prefs.PREF_BOOL && 1.134 + Services.prefs.getBoolPref("browser.aboutHome.apps")) 1.135 + doc.getElementById("apps").removeAttribute("hidden"); 1.136 + 1.137 + sendAsyncMessage("AboutHome:RequestUpdate"); 1.138 + 1.139 + doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) { 1.140 + sendAsyncMessage("AboutHome:Search", { searchData: e.detail }); 1.141 + }, true, true); 1.142 + }, 1.143 + 1.144 + onClick: function(aEvent) { 1.145 + if (!aEvent.isTrusted || // Don't trust synthetic events 1.146 + aEvent.button == 2 || aEvent.target.localName != "button") { 1.147 + return; 1.148 + } 1.149 + 1.150 + let originalTarget = aEvent.originalTarget; 1.151 + let ownerDoc = originalTarget.ownerDocument; 1.152 + if (ownerDoc.documentURI != "about:home") { 1.153 + // This shouldn't happen, but we're being defensive. 1.154 + return; 1.155 + } 1.156 + 1.157 + let elmId = originalTarget.getAttribute("id"); 1.158 + 1.159 + switch (elmId) { 1.160 + case "restorePreviousSession": 1.161 + sendAsyncMessage("AboutHome:RestorePreviousSession"); 1.162 + ownerDoc.getElementById("launcher").removeAttribute("session"); 1.163 + break; 1.164 + 1.165 + case "downloads": 1.166 + sendAsyncMessage("AboutHome:Downloads"); 1.167 + break; 1.168 + 1.169 + case "bookmarks": 1.170 + sendAsyncMessage("AboutHome:Bookmarks"); 1.171 + break; 1.172 + 1.173 + case "history": 1.174 + sendAsyncMessage("AboutHome:History"); 1.175 + break; 1.176 + 1.177 + case "apps": 1.178 + sendAsyncMessage("AboutHome:Apps"); 1.179 + break; 1.180 + 1.181 + case "addons": 1.182 + sendAsyncMessage("AboutHome:Addons"); 1.183 + break; 1.184 + 1.185 + case "sync": 1.186 + sendAsyncMessage("AboutHome:Sync"); 1.187 + break; 1.188 + 1.189 + case "settings": 1.190 + sendAsyncMessage("AboutHome:Settings"); 1.191 + break; 1.192 + } 1.193 + }, 1.194 +}; 1.195 +AboutHomeListener.init(this); 1.196 + 1.197 + 1.198 +let ContentSearchMediator = { 1.199 + 1.200 + whitelist: new Set([ 1.201 + "about:newtab", 1.202 + ]), 1.203 + 1.204 + init: function (chromeGlobal) { 1.205 + chromeGlobal.addEventListener("ContentSearchClient", this, true, true); 1.206 + addMessageListener("ContentSearch", this); 1.207 + }, 1.208 + 1.209 + handleEvent: function (event) { 1.210 + if (this._contentWhitelisted) { 1.211 + this._sendMsg(event.detail.type, event.detail.data); 1.212 + } 1.213 + }, 1.214 + 1.215 + receiveMessage: function (msg) { 1.216 + if (msg.data.type == "AddToWhitelist") { 1.217 + for (let uri of msg.data.data) { 1.218 + this.whitelist.add(uri); 1.219 + } 1.220 + this._sendMsg("AddToWhitelistAck"); 1.221 + return; 1.222 + } 1.223 + if (this._contentWhitelisted) { 1.224 + this._fireEvent(msg.data.type, msg.data.data); 1.225 + } 1.226 + }, 1.227 + 1.228 + get _contentWhitelisted() { 1.229 + return this.whitelist.has(content.document.documentURI.toLowerCase()); 1.230 + }, 1.231 + 1.232 + _sendMsg: function (type, data=null) { 1.233 + sendAsyncMessage("ContentSearch", { 1.234 + type: type, 1.235 + data: data, 1.236 + }); 1.237 + }, 1.238 + 1.239 + _fireEvent: function (type, data=null) { 1.240 + content.dispatchEvent(new content.CustomEvent("ContentSearchService", { 1.241 + detail: { 1.242 + type: type, 1.243 + data: data, 1.244 + }, 1.245 + })); 1.246 + }, 1.247 +}; 1.248 +ContentSearchMediator.init(this); 1.249 + 1.250 + 1.251 +var global = this; 1.252 + 1.253 +// Lazily load the finder code 1.254 +addMessageListener("Finder:Initialize", function () { 1.255 + let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {}); 1.256 + new RemoteFinderListener(global); 1.257 +}); 1.258 + 1.259 + 1.260 +let ClickEventHandler = { 1.261 + init: function init() { 1.262 + Cc["@mozilla.org/eventlistenerservice;1"] 1.263 + .getService(Ci.nsIEventListenerService) 1.264 + .addSystemEventListener(global, "click", this, true); 1.265 + }, 1.266 + 1.267 + handleEvent: function(event) { 1.268 + // Bug 903016: Most of this code is an unfortunate duplication from 1.269 + // contentAreaClick in browser.js. 1.270 + if (!event.isTrusted || event.defaultPrevented || event.button == 2) 1.271 + return; 1.272 + 1.273 + let [href, node] = this._hrefAndLinkNodeForClickEvent(event); 1.274 + 1.275 + let json = { button: event.button, shiftKey: event.shiftKey, 1.276 + ctrlKey: event.ctrlKey, metaKey: event.metaKey, 1.277 + altKey: event.altKey, href: null, title: null, 1.278 + bookmark: false }; 1.279 + 1.280 + if (href) { 1.281 + json.href = href; 1.282 + if (node) { 1.283 + json.title = node.getAttribute("title"); 1.284 + 1.285 + if (event.button == 0 && !event.ctrlKey && !event.shiftKey && 1.286 + !event.altKey && !event.metaKey) { 1.287 + json.bookmark = node.getAttribute("rel") == "sidebar"; 1.288 + if (json.bookmark) 1.289 + event.preventDefault(); // Need to prevent the pageload. 1.290 + } 1.291 + } 1.292 + 1.293 + sendAsyncMessage("Content:Click", json); 1.294 + return; 1.295 + } 1.296 + 1.297 + // This might be middle mouse navigation. 1.298 + if (event.button == 1) 1.299 + sendAsyncMessage("Content:Click", json); 1.300 + }, 1.301 + 1.302 + /** 1.303 + * Extracts linkNode and href for the current click target. 1.304 + * 1.305 + * @param event 1.306 + * The click event. 1.307 + * @return [href, linkNode]. 1.308 + * 1.309 + * @note linkNode will be null if the click wasn't on an anchor 1.310 + * element (or XLink). 1.311 + */ 1.312 + _hrefAndLinkNodeForClickEvent: function(event) { 1.313 + function isHTMLLink(aNode) { 1.314 + // Be consistent with what nsContextMenu.js does. 1.315 + return ((aNode instanceof content.HTMLAnchorElement && aNode.href) || 1.316 + (aNode instanceof content.HTMLAreaElement && aNode.href) || 1.317 + aNode instanceof content.HTMLLinkElement); 1.318 + } 1.319 + 1.320 + let node = event.target; 1.321 + while (node && !isHTMLLink(node)) { 1.322 + node = node.parentNode; 1.323 + } 1.324 + 1.325 + if (node) 1.326 + return [node.href, node]; 1.327 + 1.328 + // If there is no linkNode, try simple XLink. 1.329 + let href, baseURI; 1.330 + node = event.target; 1.331 + while (node && !href) { 1.332 + if (node.nodeType == content.Node.ELEMENT_NODE) { 1.333 + href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href"); 1.334 + if (href) 1.335 + baseURI = node.ownerDocument.baseURIObject; 1.336 + } 1.337 + node = node.parentNode; 1.338 + } 1.339 + 1.340 + // In case of XLink, we don't return the node we got href from since 1.341 + // callers expect <a>-like elements. 1.342 + // Note: makeURI() will throw if aUri is not a valid URI. 1.343 + return [href ? makeURI(href, null, baseURI).spec : null, null]; 1.344 + } 1.345 +}; 1.346 +ClickEventHandler.init(); 1.347 + 1.348 +ContentLinkHandler.init(this); 1.349 + 1.350 +addEventListener("DOMWebNotificationClicked", function(event) { 1.351 + sendAsyncMessage("DOMWebNotificationClicked", {}); 1.352 +}, false); 1.353 + 1.354 +let PageStyleHandler = { 1.355 + init: function() { 1.356 + addMessageListener("PageStyle:Switch", this); 1.357 + addMessageListener("PageStyle:Disable", this); 1.358 + 1.359 + // Send a CPOW to the parent so that it can synchronously request 1.360 + // the list of style sheets. 1.361 + sendSyncMessage("PageStyle:SetSyncHandler", {}, {syncHandler: this}); 1.362 + }, 1.363 + 1.364 + get markupDocumentViewer() { 1.365 + return docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer); 1.366 + }, 1.367 + 1.368 + // Called synchronously via CPOW from the parent. 1.369 + getStyleSheetInfo: function() { 1.370 + let styleSheets = this._filterStyleSheets(this.getAllStyleSheets()); 1.371 + return { 1.372 + styleSheets: styleSheets, 1.373 + authorStyleDisabled: this.markupDocumentViewer.authorStyleDisabled, 1.374 + preferredStyleSheetSet: content.document.preferredStyleSheetSet 1.375 + }; 1.376 + }, 1.377 + 1.378 + // Called synchronously via CPOW from the parent. 1.379 + getAllStyleSheets: function(frameset = content) { 1.380 + let selfSheets = Array.slice(frameset.document.styleSheets); 1.381 + let subSheets = Array.map(frameset.frames, frame => this.getAllStyleSheets(frame)); 1.382 + return selfSheets.concat(...subSheets); 1.383 + }, 1.384 + 1.385 + receiveMessage: function(msg) { 1.386 + switch (msg.name) { 1.387 + case "PageStyle:Switch": 1.388 + this.markupDocumentViewer.authorStyleDisabled = false; 1.389 + this._stylesheetSwitchAll(content, msg.data.title); 1.390 + break; 1.391 + 1.392 + case "PageStyle:Disable": 1.393 + this.markupDocumentViewer.authorStyleDisabled = true; 1.394 + break; 1.395 + } 1.396 + }, 1.397 + 1.398 + _stylesheetSwitchAll: function (frameset, title) { 1.399 + if (!title || this._stylesheetInFrame(frameset, title)) { 1.400 + this._stylesheetSwitchFrame(frameset, title); 1.401 + } 1.402 + 1.403 + for (let i = 0; i < frameset.frames.length; i++) { 1.404 + // Recurse into sub-frames. 1.405 + this._stylesheetSwitchAll(frameset.frames[i], title); 1.406 + } 1.407 + }, 1.408 + 1.409 + _stylesheetSwitchFrame: function (frame, title) { 1.410 + var docStyleSheets = frame.document.styleSheets; 1.411 + 1.412 + for (let i = 0; i < docStyleSheets.length; ++i) { 1.413 + let docStyleSheet = docStyleSheets[i]; 1.414 + if (docStyleSheet.title) { 1.415 + docStyleSheet.disabled = (docStyleSheet.title != title); 1.416 + } else if (docStyleSheet.disabled) { 1.417 + docStyleSheet.disabled = false; 1.418 + } 1.419 + } 1.420 + }, 1.421 + 1.422 + _stylesheetInFrame: function (frame, title) { 1.423 + return Array.some(frame.document.styleSheets, (styleSheet) => styleSheet.title == title); 1.424 + }, 1.425 + 1.426 + _filterStyleSheets: function(styleSheets) { 1.427 + let result = []; 1.428 + 1.429 + for (let currentStyleSheet of styleSheets) { 1.430 + if (!currentStyleSheet.title) 1.431 + continue; 1.432 + 1.433 + // Skip any stylesheets that don't match the screen media type. 1.434 + if (currentStyleSheet.media.length > 0) { 1.435 + let mediaQueryList = currentStyleSheet.media.mediaText; 1.436 + if (!content.matchMedia(mediaQueryList).matches) { 1.437 + continue; 1.438 + } 1.439 + } 1.440 + 1.441 + result.push({title: currentStyleSheet.title, 1.442 + disabled: currentStyleSheet.disabled}); 1.443 + } 1.444 + 1.445 + return result; 1.446 + }, 1.447 +}; 1.448 +PageStyleHandler.init(); 1.449 + 1.450 +let TranslationHandler = { 1.451 + init: function() { 1.452 + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) 1.453 + .getInterface(Ci.nsIWebProgress); 1.454 + webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT); 1.455 + }, 1.456 + 1.457 + /* nsIWebProgressListener implementation */ 1.458 + onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) { 1.459 + if (!aWebProgress.isTopLevel || 1.460 + !(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP)) 1.461 + return; 1.462 + 1.463 + let url = aRequest.name; 1.464 + if (!url.startsWith("http://") && !url.startsWith("https://")) 1.465 + return; 1.466 + 1.467 + // Grab a 60k sample of text from the page. 1.468 + let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"] 1.469 + .createInstance(Ci.nsIDocumentEncoder); 1.470 + encoder.init(content.document, "text/plain", encoder.SkipInvisibleContent); 1.471 + let string = encoder.encodeToStringWithMaxLength(60 * 1024); 1.472 + 1.473 + // Language detection isn't reliable on very short strings. 1.474 + if (string.length < 100) 1.475 + return; 1.476 + 1.477 + LanguageDetector.detectLanguage(string).then(result => { 1.478 + if (result.confident) 1.479 + sendAsyncMessage("LanguageDetection:Result", result.language); 1.480 + }); 1.481 + }, 1.482 + 1.483 + // Unused methods. 1.484 + onProgressChange: function() {}, 1.485 + onLocationChange: function() {}, 1.486 + onStatusChange: function() {}, 1.487 + onSecurityChange: function() {}, 1.488 + 1.489 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, 1.490 + Ci.nsISupportsWeakReference]) 1.491 +}; 1.492 + 1.493 +if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) 1.494 + TranslationHandler.init();