Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 let Cc = Components.classes;
7 let Ci = Components.interfaces;
8 let Cu = Components.utils;
10 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
11 Cu.import("resource://gre/modules/Services.jsm");
13 XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
14 "resource:///modules/ContentLinkHandler.jsm");
15 XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
16 "resource:///modules/translation/LanguageDetector.jsm");
17 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
18 "resource://gre/modules/LoginManagerContent.jsm");
19 XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
20 "resource://gre/modules/InsecurePasswordUtils.jsm");
21 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
22 "resource://gre/modules/PrivateBrowsingUtils.jsm");
23 XPCOMUtils.defineLazyModuleGetter(this, "UITour",
24 "resource:///modules/UITour.jsm");
26 // Creates a new nsIURI object.
27 function makeURI(uri, originCharset, baseURI) {
28 return Services.io.newURI(uri, originCharset, baseURI);
29 }
31 addMessageListener("Browser:HideSessionRestoreButton", function (message) {
32 // Hide session restore button on about:home
33 let doc = content.document;
34 let container;
35 if (doc.documentURI.toLowerCase() == "about:home" &&
36 (container = doc.getElementById("sessionRestoreContainer"))){
37 container.hidden = true;
38 }
39 });
41 if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
42 addEventListener("contextmenu", function (event) {
43 sendAsyncMessage("contextmenu", {}, { event: event });
44 }, false);
45 } else {
46 addEventListener("DOMFormHasPassword", function(event) {
47 InsecurePasswordUtils.checkForInsecurePasswords(event.target);
48 LoginManagerContent.onFormPassword(event);
49 });
50 addEventListener("DOMAutoComplete", function(event) {
51 LoginManagerContent.onUsernameInput(event);
52 });
53 addEventListener("blur", function(event) {
54 LoginManagerContent.onUsernameInput(event);
55 });
57 addEventListener("mozUITour", function(event) {
58 if (!Services.prefs.getBoolPref("browser.uitour.enabled"))
59 return;
61 let handled = UITour.onPageEvent(event);
62 if (handled)
63 addEventListener("pagehide", UITour);
64 }, false, true);
65 }
67 let AboutHomeListener = {
68 init: function(chromeGlobal) {
69 chromeGlobal.addEventListener('AboutHomeLoad', () => this.onPageLoad(), false, true);
70 },
72 handleEvent: function(aEvent) {
73 switch (aEvent.type) {
74 case "AboutHomeLoad":
75 this.onPageLoad();
76 break;
77 }
78 },
80 receiveMessage: function(aMessage) {
81 switch (aMessage.name) {
82 case "AboutHome:Update":
83 this.onUpdate(aMessage.data);
84 break;
85 }
86 },
88 onUpdate: function(aData) {
89 let doc = content.document;
90 if (doc.documentURI.toLowerCase() != "about:home")
91 return;
93 if (aData.showRestoreLastSession && !PrivateBrowsingUtils.isWindowPrivate(content))
94 doc.getElementById("launcher").setAttribute("session", "true");
96 // Inject search engine and snippets URL.
97 let docElt = doc.documentElement;
98 // set the following attributes BEFORE searchEngineName, which triggers to
99 // show the snippets when it's set.
100 docElt.setAttribute("snippetsURL", aData.snippetsURL);
101 if (aData.showKnowYourRights)
102 docElt.setAttribute("showKnowYourRights", "true");
103 docElt.setAttribute("snippetsVersion", aData.snippetsVersion);
104 docElt.setAttribute("searchEngineName", aData.defaultEngineName);
105 },
107 onPageLoad: function() {
108 let doc = content.document;
109 if (doc.documentURI.toLowerCase() != "about:home" ||
110 doc.documentElement.hasAttribute("hasBrowserHandlers")) {
111 return;
112 }
114 doc.documentElement.setAttribute("hasBrowserHandlers", "true");
115 let self = this;
116 addMessageListener("AboutHome:Update", self);
117 addEventListener("click", this.onClick, true);
118 addEventListener("pagehide", function onPageHide(event) {
119 if (event.target.defaultView.frameElement)
120 return;
121 removeMessageListener("AboutHome:Update", self);
122 removeEventListener("click", self.onClick, true);
123 removeEventListener("pagehide", onPageHide, true);
124 if (event.target.documentElement)
125 event.target.documentElement.removeAttribute("hasBrowserHandlers");
126 }, true);
128 // XXX bug 738646 - when Marketplace is launched, remove this statement and
129 // the hidden attribute set on the apps button in aboutHome.xhtml
130 if (Services.prefs.getPrefType("browser.aboutHome.apps") == Services.prefs.PREF_BOOL &&
131 Services.prefs.getBoolPref("browser.aboutHome.apps"))
132 doc.getElementById("apps").removeAttribute("hidden");
134 sendAsyncMessage("AboutHome:RequestUpdate");
136 doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) {
137 sendAsyncMessage("AboutHome:Search", { searchData: e.detail });
138 }, true, true);
139 },
141 onClick: function(aEvent) {
142 if (!aEvent.isTrusted || // Don't trust synthetic events
143 aEvent.button == 2 || aEvent.target.localName != "button") {
144 return;
145 }
147 let originalTarget = aEvent.originalTarget;
148 let ownerDoc = originalTarget.ownerDocument;
149 if (ownerDoc.documentURI != "about:home") {
150 // This shouldn't happen, but we're being defensive.
151 return;
152 }
154 let elmId = originalTarget.getAttribute("id");
156 switch (elmId) {
157 case "restorePreviousSession":
158 sendAsyncMessage("AboutHome:RestorePreviousSession");
159 ownerDoc.getElementById("launcher").removeAttribute("session");
160 break;
162 case "downloads":
163 sendAsyncMessage("AboutHome:Downloads");
164 break;
166 case "bookmarks":
167 sendAsyncMessage("AboutHome:Bookmarks");
168 break;
170 case "history":
171 sendAsyncMessage("AboutHome:History");
172 break;
174 case "apps":
175 sendAsyncMessage("AboutHome:Apps");
176 break;
178 case "addons":
179 sendAsyncMessage("AboutHome:Addons");
180 break;
182 case "sync":
183 sendAsyncMessage("AboutHome:Sync");
184 break;
186 case "settings":
187 sendAsyncMessage("AboutHome:Settings");
188 break;
189 }
190 },
191 };
192 AboutHomeListener.init(this);
195 let ContentSearchMediator = {
197 whitelist: new Set([
198 "about:newtab",
199 ]),
201 init: function (chromeGlobal) {
202 chromeGlobal.addEventListener("ContentSearchClient", this, true, true);
203 addMessageListener("ContentSearch", this);
204 },
206 handleEvent: function (event) {
207 if (this._contentWhitelisted) {
208 this._sendMsg(event.detail.type, event.detail.data);
209 }
210 },
212 receiveMessage: function (msg) {
213 if (msg.data.type == "AddToWhitelist") {
214 for (let uri of msg.data.data) {
215 this.whitelist.add(uri);
216 }
217 this._sendMsg("AddToWhitelistAck");
218 return;
219 }
220 if (this._contentWhitelisted) {
221 this._fireEvent(msg.data.type, msg.data.data);
222 }
223 },
225 get _contentWhitelisted() {
226 return this.whitelist.has(content.document.documentURI.toLowerCase());
227 },
229 _sendMsg: function (type, data=null) {
230 sendAsyncMessage("ContentSearch", {
231 type: type,
232 data: data,
233 });
234 },
236 _fireEvent: function (type, data=null) {
237 content.dispatchEvent(new content.CustomEvent("ContentSearchService", {
238 detail: {
239 type: type,
240 data: data,
241 },
242 }));
243 },
244 };
245 ContentSearchMediator.init(this);
248 var global = this;
250 // Lazily load the finder code
251 addMessageListener("Finder:Initialize", function () {
252 let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {});
253 new RemoteFinderListener(global);
254 });
257 let ClickEventHandler = {
258 init: function init() {
259 Cc["@mozilla.org/eventlistenerservice;1"]
260 .getService(Ci.nsIEventListenerService)
261 .addSystemEventListener(global, "click", this, true);
262 },
264 handleEvent: function(event) {
265 // Bug 903016: Most of this code is an unfortunate duplication from
266 // contentAreaClick in browser.js.
267 if (!event.isTrusted || event.defaultPrevented || event.button == 2)
268 return;
270 let [href, node] = this._hrefAndLinkNodeForClickEvent(event);
272 let json = { button: event.button, shiftKey: event.shiftKey,
273 ctrlKey: event.ctrlKey, metaKey: event.metaKey,
274 altKey: event.altKey, href: null, title: null,
275 bookmark: false };
277 if (href) {
278 json.href = href;
279 if (node) {
280 json.title = node.getAttribute("title");
282 if (event.button == 0 && !event.ctrlKey && !event.shiftKey &&
283 !event.altKey && !event.metaKey) {
284 json.bookmark = node.getAttribute("rel") == "sidebar";
285 if (json.bookmark)
286 event.preventDefault(); // Need to prevent the pageload.
287 }
288 }
290 sendAsyncMessage("Content:Click", json);
291 return;
292 }
294 // This might be middle mouse navigation.
295 if (event.button == 1)
296 sendAsyncMessage("Content:Click", json);
297 },
299 /**
300 * Extracts linkNode and href for the current click target.
301 *
302 * @param event
303 * The click event.
304 * @return [href, linkNode].
305 *
306 * @note linkNode will be null if the click wasn't on an anchor
307 * element (or XLink).
308 */
309 _hrefAndLinkNodeForClickEvent: function(event) {
310 function isHTMLLink(aNode) {
311 // Be consistent with what nsContextMenu.js does.
312 return ((aNode instanceof content.HTMLAnchorElement && aNode.href) ||
313 (aNode instanceof content.HTMLAreaElement && aNode.href) ||
314 aNode instanceof content.HTMLLinkElement);
315 }
317 let node = event.target;
318 while (node && !isHTMLLink(node)) {
319 node = node.parentNode;
320 }
322 if (node)
323 return [node.href, node];
325 // If there is no linkNode, try simple XLink.
326 let href, baseURI;
327 node = event.target;
328 while (node && !href) {
329 if (node.nodeType == content.Node.ELEMENT_NODE) {
330 href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
331 if (href)
332 baseURI = node.ownerDocument.baseURIObject;
333 }
334 node = node.parentNode;
335 }
337 // In case of XLink, we don't return the node we got href from since
338 // callers expect <a>-like elements.
339 // Note: makeURI() will throw if aUri is not a valid URI.
340 return [href ? makeURI(href, null, baseURI).spec : null, null];
341 }
342 };
343 ClickEventHandler.init();
345 ContentLinkHandler.init(this);
347 addEventListener("DOMWebNotificationClicked", function(event) {
348 sendAsyncMessage("DOMWebNotificationClicked", {});
349 }, false);
351 let PageStyleHandler = {
352 init: function() {
353 addMessageListener("PageStyle:Switch", this);
354 addMessageListener("PageStyle:Disable", this);
356 // Send a CPOW to the parent so that it can synchronously request
357 // the list of style sheets.
358 sendSyncMessage("PageStyle:SetSyncHandler", {}, {syncHandler: this});
359 },
361 get markupDocumentViewer() {
362 return docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
363 },
365 // Called synchronously via CPOW from the parent.
366 getStyleSheetInfo: function() {
367 let styleSheets = this._filterStyleSheets(this.getAllStyleSheets());
368 return {
369 styleSheets: styleSheets,
370 authorStyleDisabled: this.markupDocumentViewer.authorStyleDisabled,
371 preferredStyleSheetSet: content.document.preferredStyleSheetSet
372 };
373 },
375 // Called synchronously via CPOW from the parent.
376 getAllStyleSheets: function(frameset = content) {
377 let selfSheets = Array.slice(frameset.document.styleSheets);
378 let subSheets = Array.map(frameset.frames, frame => this.getAllStyleSheets(frame));
379 return selfSheets.concat(...subSheets);
380 },
382 receiveMessage: function(msg) {
383 switch (msg.name) {
384 case "PageStyle:Switch":
385 this.markupDocumentViewer.authorStyleDisabled = false;
386 this._stylesheetSwitchAll(content, msg.data.title);
387 break;
389 case "PageStyle:Disable":
390 this.markupDocumentViewer.authorStyleDisabled = true;
391 break;
392 }
393 },
395 _stylesheetSwitchAll: function (frameset, title) {
396 if (!title || this._stylesheetInFrame(frameset, title)) {
397 this._stylesheetSwitchFrame(frameset, title);
398 }
400 for (let i = 0; i < frameset.frames.length; i++) {
401 // Recurse into sub-frames.
402 this._stylesheetSwitchAll(frameset.frames[i], title);
403 }
404 },
406 _stylesheetSwitchFrame: function (frame, title) {
407 var docStyleSheets = frame.document.styleSheets;
409 for (let i = 0; i < docStyleSheets.length; ++i) {
410 let docStyleSheet = docStyleSheets[i];
411 if (docStyleSheet.title) {
412 docStyleSheet.disabled = (docStyleSheet.title != title);
413 } else if (docStyleSheet.disabled) {
414 docStyleSheet.disabled = false;
415 }
416 }
417 },
419 _stylesheetInFrame: function (frame, title) {
420 return Array.some(frame.document.styleSheets, (styleSheet) => styleSheet.title == title);
421 },
423 _filterStyleSheets: function(styleSheets) {
424 let result = [];
426 for (let currentStyleSheet of styleSheets) {
427 if (!currentStyleSheet.title)
428 continue;
430 // Skip any stylesheets that don't match the screen media type.
431 if (currentStyleSheet.media.length > 0) {
432 let mediaQueryList = currentStyleSheet.media.mediaText;
433 if (!content.matchMedia(mediaQueryList).matches) {
434 continue;
435 }
436 }
438 result.push({title: currentStyleSheet.title,
439 disabled: currentStyleSheet.disabled});
440 }
442 return result;
443 },
444 };
445 PageStyleHandler.init();
447 let TranslationHandler = {
448 init: function() {
449 let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
450 .getInterface(Ci.nsIWebProgress);
451 webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
452 },
454 /* nsIWebProgressListener implementation */
455 onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
456 if (!aWebProgress.isTopLevel ||
457 !(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP))
458 return;
460 let url = aRequest.name;
461 if (!url.startsWith("http://") && !url.startsWith("https://"))
462 return;
464 // Grab a 60k sample of text from the page.
465 let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
466 .createInstance(Ci.nsIDocumentEncoder);
467 encoder.init(content.document, "text/plain", encoder.SkipInvisibleContent);
468 let string = encoder.encodeToStringWithMaxLength(60 * 1024);
470 // Language detection isn't reliable on very short strings.
471 if (string.length < 100)
472 return;
474 LanguageDetector.detectLanguage(string).then(result => {
475 if (result.confident)
476 sendAsyncMessage("LanguageDetection:Result", result.language);
477 });
478 },
480 // Unused methods.
481 onProgressChange: function() {},
482 onLocationChange: function() {},
483 onStatusChange: function() {},
484 onSecurityChange: function() {},
486 QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
487 Ci.nsISupportsWeakReference])
488 };
490 if (Services.prefs.getBoolPref("browser.translation.detectLanguage"))
491 TranslationHandler.init();