|
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/. |
|
5 |
|
6 let Ci = Components.interfaces; |
|
7 let Cu = Components.utils; |
|
8 |
|
9 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
10 Cu.import("resource://gre/modules/NotificationDB.jsm"); |
|
11 Cu.import("resource:///modules/RecentWindow.jsm"); |
|
12 Cu.import("resource://gre/modules/WindowsPrefSync.jsm"); |
|
13 |
|
14 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", |
|
15 "resource://gre/modules/BrowserUtils.jsm"); |
|
16 XPCOMUtils.defineLazyModuleGetter(this, "Task", |
|
17 "resource://gre/modules/Task.jsm"); |
|
18 XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu", |
|
19 "resource://gre/modules/CharsetMenu.jsm"); |
|
20 XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils", |
|
21 "resource://gre/modules/ShortcutUtils.jsm"); |
|
22 |
|
23 const nsIWebNavigation = Ci.nsIWebNavigation; |
|
24 |
|
25 var gLastBrowserCharset = null; |
|
26 var gPrevCharset = null; |
|
27 var gProxyFavIcon = null; |
|
28 var gLastValidURLStr = ""; |
|
29 var gInPrintPreviewMode = false; |
|
30 var gContextMenu = null; // nsContextMenu instance |
|
31 var gMultiProcessBrowser = false; |
|
32 |
|
33 #ifndef XP_MACOSX |
|
34 var gEditUIVisible = true; |
|
35 #endif |
|
36 |
|
37 [ |
|
38 ["gBrowser", "content"], |
|
39 ["gNavToolbox", "navigator-toolbox"], |
|
40 ["gURLBar", "urlbar"], |
|
41 ["gNavigatorBundle", "bundle_browser"] |
|
42 ].forEach(function (elementGlobal) { |
|
43 var [name, id] = elementGlobal; |
|
44 window.__defineGetter__(name, function () { |
|
45 var element = document.getElementById(id); |
|
46 if (!element) |
|
47 return null; |
|
48 delete window[name]; |
|
49 return window[name] = element; |
|
50 }); |
|
51 window.__defineSetter__(name, function (val) { |
|
52 delete window[name]; |
|
53 return window[name] = val; |
|
54 }); |
|
55 }); |
|
56 |
|
57 // Smart getter for the findbar. If you don't wish to force the creation of |
|
58 // the findbar, check gFindBarInitialized first. |
|
59 |
|
60 this.__defineGetter__("gFindBar", function() { |
|
61 return window.gBrowser.getFindBar(); |
|
62 }); |
|
63 |
|
64 this.__defineGetter__("gFindBarInitialized", function() { |
|
65 return window.gBrowser.isFindBarInitialized(); |
|
66 }); |
|
67 |
|
68 XPCOMUtils.defineLazyGetter(this, "gPrefService", function() { |
|
69 return Services.prefs; |
|
70 }); |
|
71 |
|
72 this.__defineGetter__("AddonManager", function() { |
|
73 let tmp = {}; |
|
74 Cu.import("resource://gre/modules/AddonManager.jsm", tmp); |
|
75 return this.AddonManager = tmp.AddonManager; |
|
76 }); |
|
77 this.__defineSetter__("AddonManager", function (val) { |
|
78 delete this.AddonManager; |
|
79 return this.AddonManager = val; |
|
80 }); |
|
81 |
|
82 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", |
|
83 "resource://gre/modules/PluralForm.jsm"); |
|
84 |
|
85 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", |
|
86 "resource://gre/modules/TelemetryStopwatch.jsm"); |
|
87 |
|
88 XPCOMUtils.defineLazyGetter(this, "gCustomizeMode", function() { |
|
89 let scope = {}; |
|
90 Cu.import("resource:///modules/CustomizeMode.jsm", scope); |
|
91 return new scope.CustomizeMode(window); |
|
92 }); |
|
93 |
|
94 #ifdef MOZ_SERVICES_SYNC |
|
95 XPCOMUtils.defineLazyModuleGetter(this, "Weave", |
|
96 "resource://services-sync/main.js"); |
|
97 #endif |
|
98 |
|
99 XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () { |
|
100 let tmp = {}; |
|
101 Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp); |
|
102 try { |
|
103 return new tmp.PopupNotifications(gBrowser, |
|
104 document.getElementById("notification-popup"), |
|
105 document.getElementById("notification-popup-box")); |
|
106 } catch (ex) { |
|
107 Cu.reportError(ex); |
|
108 return null; |
|
109 } |
|
110 }); |
|
111 |
|
112 XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() { |
|
113 let tmp = {}; |
|
114 Cu.import("resource:///modules/devtools/DeveloperToolbar.jsm", tmp); |
|
115 return new tmp.DeveloperToolbar(window, document.getElementById("developer-toolbar")); |
|
116 }); |
|
117 |
|
118 XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() { |
|
119 let tmp = {}; |
|
120 Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", tmp); |
|
121 return tmp.BrowserToolboxProcess; |
|
122 }); |
|
123 |
|
124 XPCOMUtils.defineLazyModuleGetter(this, "Social", |
|
125 "resource:///modules/Social.jsm"); |
|
126 |
|
127 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs", |
|
128 "resource://gre/modules/PageThumbs.jsm"); |
|
129 |
|
130 #ifdef MOZ_SAFE_BROWSING |
|
131 XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing", |
|
132 "resource://gre/modules/SafeBrowsing.jsm"); |
|
133 #endif |
|
134 |
|
135 XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader", |
|
136 "resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader"); |
|
137 |
|
138 XPCOMUtils.defineLazyModuleGetter(this, "gCustomizationTabPreloader", |
|
139 "resource:///modules/CustomizationTabPreloader.jsm", "CustomizationTabPreloader"); |
|
140 |
|
141 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", |
|
142 "resource://gre/modules/PrivateBrowsingUtils.jsm"); |
|
143 |
|
144 XPCOMUtils.defineLazyModuleGetter(this, "Translation", |
|
145 "resource:///modules/translation/Translation.jsm"); |
|
146 |
|
147 XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions", |
|
148 "resource:///modules/SitePermissions.jsm"); |
|
149 |
|
150 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore", |
|
151 "resource:///modules/sessionstore/SessionStore.jsm"); |
|
152 |
|
153 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts", |
|
154 "resource://gre/modules/FxAccounts.jsm"); |
|
155 |
|
156 #ifdef MOZ_CRASHREPORTER |
|
157 XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter", |
|
158 "resource:///modules/TabCrashReporter.jsm"); |
|
159 #endif |
|
160 |
|
161 let gInitialPages = [ |
|
162 "about:blank", |
|
163 "about:newtab", |
|
164 "about:home", |
|
165 "about:privatebrowsing", |
|
166 "about:welcomeback", |
|
167 "about:sessionrestore" |
|
168 ]; |
|
169 |
|
170 #include browser-addons.js |
|
171 #include browser-customization.js |
|
172 #include browser-feeds.js |
|
173 #include browser-fullScreen.js |
|
174 #include browser-fullZoom.js |
|
175 #include browser-places.js |
|
176 #include browser-plugins.js |
|
177 #include browser-safebrowsing.js |
|
178 #include browser-social.js |
|
179 #include browser-tabPreviews.js |
|
180 #include browser-tabview.js |
|
181 #include browser-thumbnails.js |
|
182 #include browser-webrtcUI.js |
|
183 #include browser-gestureSupport.js |
|
184 |
|
185 #ifdef MOZ_DATA_REPORTING |
|
186 #include browser-data-submission-info-bar.js |
|
187 #endif |
|
188 |
|
189 #ifdef MOZ_SERVICES_SYNC |
|
190 #include browser-syncui.js |
|
191 #endif |
|
192 |
|
193 #include browser-fxaccounts.js |
|
194 |
|
195 XPCOMUtils.defineLazyGetter(this, "Win7Features", function () { |
|
196 #ifdef XP_WIN |
|
197 // Bug 666808 - AeroPeek support for e10s |
|
198 if (gMultiProcessBrowser) |
|
199 return null; |
|
200 |
|
201 const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1"; |
|
202 if (WINTASKBAR_CONTRACTID in Cc && |
|
203 Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) { |
|
204 let AeroPeek = Cu.import("resource:///modules/WindowsPreviewPerTab.jsm", {}).AeroPeek; |
|
205 return { |
|
206 onOpenWindow: function () { |
|
207 AeroPeek.onOpenWindow(window); |
|
208 }, |
|
209 onCloseWindow: function () { |
|
210 AeroPeek.onCloseWindow(window); |
|
211 } |
|
212 }; |
|
213 } |
|
214 #endif |
|
215 return null; |
|
216 }); |
|
217 |
|
218 #ifdef MOZ_CRASHREPORTER |
|
219 XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter", |
|
220 "@mozilla.org/xre/app-info;1", |
|
221 "nsICrashReporter"); |
|
222 #endif |
|
223 |
|
224 XPCOMUtils.defineLazyGetter(this, "PageMenu", function() { |
|
225 let tmp = {}; |
|
226 Cu.import("resource://gre/modules/PageMenu.jsm", tmp); |
|
227 return new tmp.PageMenu(); |
|
228 }); |
|
229 |
|
230 /** |
|
231 * We can avoid adding multiple load event listeners and save some time by adding |
|
232 * one listener that calls all real handlers. |
|
233 */ |
|
234 function pageShowEventHandlers(persisted) { |
|
235 charsetLoadListener(); |
|
236 XULBrowserWindow.asyncUpdateUI(); |
|
237 |
|
238 // The PluginClickToPlay events are not fired when navigating using the |
|
239 // BF cache. |persisted| is true when the page is loaded from the |
|
240 // BF cache, so this code reshows the notification if necessary. |
|
241 if (persisted) |
|
242 gPluginHandler.reshowClickToPlayNotification(); |
|
243 } |
|
244 |
|
245 function UpdateBackForwardCommands(aWebNavigation) { |
|
246 var backBroadcaster = document.getElementById("Browser:Back"); |
|
247 var forwardBroadcaster = document.getElementById("Browser:Forward"); |
|
248 |
|
249 // Avoid setting attributes on broadcasters if the value hasn't changed! |
|
250 // Remember, guys, setting attributes on elements is expensive! They |
|
251 // get inherited into anonymous content, broadcast to other widgets, etc.! |
|
252 // Don't do it if the value hasn't changed! - dwh |
|
253 |
|
254 var backDisabled = backBroadcaster.hasAttribute("disabled"); |
|
255 var forwardDisabled = forwardBroadcaster.hasAttribute("disabled"); |
|
256 if (backDisabled == aWebNavigation.canGoBack) { |
|
257 if (backDisabled) |
|
258 backBroadcaster.removeAttribute("disabled"); |
|
259 else |
|
260 backBroadcaster.setAttribute("disabled", true); |
|
261 } |
|
262 |
|
263 if (forwardDisabled == aWebNavigation.canGoForward) { |
|
264 if (forwardDisabled) |
|
265 forwardBroadcaster.removeAttribute("disabled"); |
|
266 else |
|
267 forwardBroadcaster.setAttribute("disabled", true); |
|
268 } |
|
269 } |
|
270 |
|
271 /** |
|
272 * Click-and-Hold implementation for the Back and Forward buttons |
|
273 * XXXmano: should this live in toolbarbutton.xml? |
|
274 */ |
|
275 function SetClickAndHoldHandlers() { |
|
276 var timer; |
|
277 |
|
278 function openMenu(aButton) { |
|
279 cancelHold(aButton); |
|
280 aButton.firstChild.hidden = false; |
|
281 aButton.open = true; |
|
282 } |
|
283 |
|
284 function mousedownHandler(aEvent) { |
|
285 if (aEvent.button != 0 || |
|
286 aEvent.currentTarget.open || |
|
287 aEvent.currentTarget.disabled) |
|
288 return; |
|
289 |
|
290 // Prevent the menupopup from opening immediately |
|
291 aEvent.currentTarget.firstChild.hidden = true; |
|
292 |
|
293 aEvent.currentTarget.addEventListener("mouseout", mouseoutHandler, false); |
|
294 aEvent.currentTarget.addEventListener("mouseup", mouseupHandler, false); |
|
295 timer = setTimeout(openMenu, 500, aEvent.currentTarget); |
|
296 } |
|
297 |
|
298 function mouseoutHandler(aEvent) { |
|
299 let buttonRect = aEvent.currentTarget.getBoundingClientRect(); |
|
300 if (aEvent.clientX >= buttonRect.left && |
|
301 aEvent.clientX <= buttonRect.right && |
|
302 aEvent.clientY >= buttonRect.bottom) |
|
303 openMenu(aEvent.currentTarget); |
|
304 else |
|
305 cancelHold(aEvent.currentTarget); |
|
306 } |
|
307 |
|
308 function mouseupHandler(aEvent) { |
|
309 cancelHold(aEvent.currentTarget); |
|
310 } |
|
311 |
|
312 function cancelHold(aButton) { |
|
313 clearTimeout(timer); |
|
314 aButton.removeEventListener("mouseout", mouseoutHandler, false); |
|
315 aButton.removeEventListener("mouseup", mouseupHandler, false); |
|
316 } |
|
317 |
|
318 function clickHandler(aEvent) { |
|
319 if (aEvent.button == 0 && |
|
320 aEvent.target == aEvent.currentTarget && |
|
321 !aEvent.currentTarget.open && |
|
322 !aEvent.currentTarget.disabled) { |
|
323 let cmdEvent = document.createEvent("xulcommandevent"); |
|
324 cmdEvent.initCommandEvent("command", true, true, window, 0, |
|
325 aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey, |
|
326 aEvent.metaKey, null); |
|
327 aEvent.currentTarget.dispatchEvent(cmdEvent); |
|
328 } |
|
329 } |
|
330 |
|
331 function _addClickAndHoldListenersOnElement(aElm) { |
|
332 aElm.addEventListener("mousedown", mousedownHandler, true); |
|
333 aElm.addEventListener("click", clickHandler, true); |
|
334 } |
|
335 |
|
336 // Bug 414797: Clone the back/forward buttons' context menu into both buttons. |
|
337 let popup = document.getElementById("backForwardMenu").cloneNode(true); |
|
338 popup.removeAttribute("id"); |
|
339 // Prevent the back/forward buttons' context attributes from being inherited. |
|
340 popup.setAttribute("context", ""); |
|
341 |
|
342 let backButton = document.getElementById("back-button"); |
|
343 backButton.setAttribute("type", "menu"); |
|
344 backButton.appendChild(popup); |
|
345 _addClickAndHoldListenersOnElement(backButton); |
|
346 |
|
347 let forwardButton = document.getElementById("forward-button"); |
|
348 popup = popup.cloneNode(true); |
|
349 forwardButton.setAttribute("type", "menu"); |
|
350 forwardButton.appendChild(popup); |
|
351 _addClickAndHoldListenersOnElement(forwardButton); |
|
352 } |
|
353 |
|
354 const gSessionHistoryObserver = { |
|
355 observe: function(subject, topic, data) |
|
356 { |
|
357 if (topic != "browser:purge-session-history") |
|
358 return; |
|
359 |
|
360 var backCommand = document.getElementById("Browser:Back"); |
|
361 backCommand.setAttribute("disabled", "true"); |
|
362 var fwdCommand = document.getElementById("Browser:Forward"); |
|
363 fwdCommand.setAttribute("disabled", "true"); |
|
364 |
|
365 // Hide session restore button on about:home |
|
366 window.messageManager.broadcastAsyncMessage("Browser:HideSessionRestoreButton"); |
|
367 |
|
368 if (gURLBar) { |
|
369 // Clear undo history of the URL bar |
|
370 gURLBar.editor.transactionManager.clear() |
|
371 } |
|
372 } |
|
373 }; |
|
374 |
|
375 /** |
|
376 * Given a starting docshell and a URI to look up, find the docshell the URI |
|
377 * is loaded in. |
|
378 * @param aDocument |
|
379 * A document to find instead of using just a URI - this is more specific. |
|
380 * @param aDocShell |
|
381 * The doc shell to start at |
|
382 * @param aSoughtURI |
|
383 * The URI that we're looking for |
|
384 * @returns The doc shell that the sought URI is loaded in. Can be in |
|
385 * subframes. |
|
386 */ |
|
387 function findChildShell(aDocument, aDocShell, aSoughtURI) { |
|
388 aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation); |
|
389 aDocShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor); |
|
390 var doc = aDocShell.getInterface(Components.interfaces.nsIDOMDocument); |
|
391 if ((aDocument && doc == aDocument) || |
|
392 (aSoughtURI && aSoughtURI.spec == aDocShell.currentURI.spec)) |
|
393 return aDocShell; |
|
394 |
|
395 var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem); |
|
396 for (var i = 0; i < node.childCount; ++i) { |
|
397 var docShell = node.getChildAt(i); |
|
398 docShell = findChildShell(aDocument, docShell, aSoughtURI); |
|
399 if (docShell) |
|
400 return docShell; |
|
401 } |
|
402 return null; |
|
403 } |
|
404 |
|
405 var gPopupBlockerObserver = { |
|
406 _reportButton: null, |
|
407 |
|
408 onReportButtonClick: function (aEvent) |
|
409 { |
|
410 if (aEvent.button != 0 || aEvent.target != this._reportButton) |
|
411 return; |
|
412 |
|
413 document.getElementById("blockedPopupOptions") |
|
414 .openPopup(this._reportButton, "after_end", 0, 2, false, false, aEvent); |
|
415 }, |
|
416 |
|
417 handleEvent: function (aEvent) |
|
418 { |
|
419 if (aEvent.originalTarget != gBrowser.selectedBrowser) |
|
420 return; |
|
421 |
|
422 if (!this._reportButton && gURLBar) |
|
423 this._reportButton = document.getElementById("page-report-button"); |
|
424 |
|
425 if (!gBrowser.selectedBrowser.blockedPopups) { |
|
426 // Hide the icon in the location bar (if the location bar exists) |
|
427 if (gURLBar) |
|
428 this._reportButton.hidden = true; |
|
429 return; |
|
430 } |
|
431 |
|
432 if (gURLBar) |
|
433 this._reportButton.hidden = false; |
|
434 |
|
435 // Only show the notification again if we've not already shown it. Since |
|
436 // notifications are per-browser, we don't need to worry about re-adding |
|
437 // it. |
|
438 if (!gBrowser.selectedBrowser.blockedPopups.reported) { |
|
439 if (gPrefService.getBoolPref("privacy.popups.showBrowserMessage")) { |
|
440 var brandBundle = document.getElementById("bundle_brand"); |
|
441 var brandShortName = brandBundle.getString("brandShortName"); |
|
442 var popupCount = gBrowser.selectedBrowser.blockedPopups.length; |
|
443 #ifdef XP_WIN |
|
444 var popupButtonText = gNavigatorBundle.getString("popupWarningButton"); |
|
445 var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButton.accesskey"); |
|
446 #else |
|
447 var popupButtonText = gNavigatorBundle.getString("popupWarningButtonUnix"); |
|
448 var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButtonUnix.accesskey"); |
|
449 #endif |
|
450 var messageBase = gNavigatorBundle.getString("popupWarning.message"); |
|
451 var message = PluralForm.get(popupCount, messageBase) |
|
452 .replace("#1", brandShortName) |
|
453 .replace("#2", popupCount); |
|
454 |
|
455 var notificationBox = gBrowser.getNotificationBox(); |
|
456 var notification = notificationBox.getNotificationWithValue("popup-blocked"); |
|
457 if (notification) { |
|
458 notification.label = message; |
|
459 } |
|
460 else { |
|
461 var buttons = [{ |
|
462 label: popupButtonText, |
|
463 accessKey: popupButtonAccesskey, |
|
464 popup: "blockedPopupOptions", |
|
465 callback: null |
|
466 }]; |
|
467 |
|
468 const priority = notificationBox.PRIORITY_WARNING_MEDIUM; |
|
469 notificationBox.appendNotification(message, "popup-blocked", |
|
470 "chrome://browser/skin/Info.png", |
|
471 priority, buttons); |
|
472 } |
|
473 } |
|
474 |
|
475 // Record the fact that we've reported this blocked popup, so we don't |
|
476 // show it again. |
|
477 gBrowser.selectedBrowser.blockedPopups.reported = true; |
|
478 } |
|
479 }, |
|
480 |
|
481 toggleAllowPopupsForSite: function (aEvent) |
|
482 { |
|
483 var pm = Services.perms; |
|
484 var shouldBlock = aEvent.target.getAttribute("block") == "true"; |
|
485 var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION; |
|
486 pm.add(gBrowser.currentURI, "popup", perm); |
|
487 |
|
488 gBrowser.getNotificationBox().removeCurrentNotification(); |
|
489 }, |
|
490 |
|
491 fillPopupList: function (aEvent) |
|
492 { |
|
493 // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites |
|
494 // we should really walk the blockedPopups and create a list of "allow for <host>" |
|
495 // menuitems for the common subset of hosts present in the report, this will |
|
496 // make us frame-safe. |
|
497 // |
|
498 // XXXjst - Note that when this is fixed to work with multi-framed sites, |
|
499 // also back out the fix for bug 343772 where |
|
500 // nsGlobalWindow::CheckOpenAllow() was changed to also |
|
501 // check if the top window's location is whitelisted. |
|
502 let browser = gBrowser.selectedBrowser; |
|
503 var uri = browser.currentURI; |
|
504 var blockedPopupAllowSite = document.getElementById("blockedPopupAllowSite"); |
|
505 try { |
|
506 blockedPopupAllowSite.removeAttribute("hidden"); |
|
507 |
|
508 var pm = Services.perms; |
|
509 if (pm.testPermission(uri, "popup") == pm.ALLOW_ACTION) { |
|
510 // Offer an item to block popups for this site, if a whitelist entry exists |
|
511 // already for it. |
|
512 let blockString = gNavigatorBundle.getFormattedString("popupBlock", [uri.host || uri.spec]); |
|
513 blockedPopupAllowSite.setAttribute("label", blockString); |
|
514 blockedPopupAllowSite.setAttribute("block", "true"); |
|
515 } |
|
516 else { |
|
517 // Offer an item to allow popups for this site |
|
518 let allowString = gNavigatorBundle.getFormattedString("popupAllow", [uri.host || uri.spec]); |
|
519 blockedPopupAllowSite.setAttribute("label", allowString); |
|
520 blockedPopupAllowSite.removeAttribute("block"); |
|
521 } |
|
522 } |
|
523 catch (e) { |
|
524 blockedPopupAllowSite.setAttribute("hidden", "true"); |
|
525 } |
|
526 |
|
527 if (PrivateBrowsingUtils.isWindowPrivate(window)) |
|
528 blockedPopupAllowSite.setAttribute("disabled", "true"); |
|
529 else |
|
530 blockedPopupAllowSite.removeAttribute("disabled"); |
|
531 |
|
532 var foundUsablePopupURI = false; |
|
533 var blockedPopups = browser.blockedPopups; |
|
534 if (blockedPopups) { |
|
535 for (let i = 0; i < blockedPopups.length; i++) { |
|
536 let blockedPopup = blockedPopups[i]; |
|
537 |
|
538 // popupWindowURI will be null if the file picker popup is blocked. |
|
539 // xxxdz this should make the option say "Show file picker" and do it (Bug 590306) |
|
540 if (!blockedPopup.popupWindowURI) |
|
541 continue; |
|
542 var popupURIspec = blockedPopup.popupWindowURI; |
|
543 |
|
544 // Sometimes the popup URI that we get back from the blockedPopup |
|
545 // isn't useful (for instance, netscape.com's popup URI ends up |
|
546 // being "http://www.netscape.com", which isn't really the URI of |
|
547 // the popup they're trying to show). This isn't going to be |
|
548 // useful to the user, so we won't create a menu item for it. |
|
549 if (popupURIspec == "" || popupURIspec == "about:blank" || |
|
550 popupURIspec == uri.spec) |
|
551 continue; |
|
552 |
|
553 // Because of the short-circuit above, we may end up in a situation |
|
554 // in which we don't have any usable popup addresses to show in |
|
555 // the menu, and therefore we shouldn't show the separator. However, |
|
556 // since we got past the short-circuit, we must've found at least |
|
557 // one usable popup URI and thus we'll turn on the separator later. |
|
558 foundUsablePopupURI = true; |
|
559 |
|
560 var menuitem = document.createElement("menuitem"); |
|
561 var label = gNavigatorBundle.getFormattedString("popupShowPopupPrefix", |
|
562 [popupURIspec]); |
|
563 menuitem.setAttribute("label", label); |
|
564 menuitem.setAttribute("popupWindowURI", popupURIspec); |
|
565 menuitem.setAttribute("popupWindowFeatures", blockedPopup.popupWindowFeatures); |
|
566 menuitem.setAttribute("popupWindowName", blockedPopup.popupWindowName); |
|
567 menuitem.setAttribute("oncommand", "gPopupBlockerObserver.showBlockedPopup(event);"); |
|
568 menuitem.setAttribute("popupReportIndex", i); |
|
569 menuitem.popupReportBrowser = browser; |
|
570 aEvent.target.appendChild(menuitem); |
|
571 } |
|
572 } |
|
573 |
|
574 // Show or hide the separator, depending on whether we added any |
|
575 // showable popup addresses to the menu. |
|
576 var blockedPopupsSeparator = |
|
577 document.getElementById("blockedPopupsSeparator"); |
|
578 if (foundUsablePopupURI) |
|
579 blockedPopupsSeparator.removeAttribute("hidden"); |
|
580 else |
|
581 blockedPopupsSeparator.setAttribute("hidden", true); |
|
582 |
|
583 var blockedPopupDontShowMessage = document.getElementById("blockedPopupDontShowMessage"); |
|
584 var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage"); |
|
585 blockedPopupDontShowMessage.setAttribute("checked", !showMessage); |
|
586 if (aEvent.target.anchorNode.id == "page-report-button") { |
|
587 aEvent.target.anchorNode.setAttribute("open", "true"); |
|
588 blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromLocationbar")); |
|
589 } else |
|
590 blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromMessage")); |
|
591 }, |
|
592 |
|
593 onPopupHiding: function (aEvent) { |
|
594 if (aEvent.target.anchorNode.id == "page-report-button") |
|
595 aEvent.target.anchorNode.removeAttribute("open"); |
|
596 |
|
597 let item = aEvent.target.lastChild; |
|
598 while (item && item.getAttribute("observes") != "blockedPopupsSeparator") { |
|
599 let next = item.previousSibling; |
|
600 item.parentNode.removeChild(item); |
|
601 item = next; |
|
602 } |
|
603 }, |
|
604 |
|
605 showBlockedPopup: function (aEvent) |
|
606 { |
|
607 var target = aEvent.target; |
|
608 var popupReportIndex = target.getAttribute("popupReportIndex"); |
|
609 let browser = target.popupReportBrowser; |
|
610 browser.unblockPopup(popupReportIndex); |
|
611 }, |
|
612 |
|
613 editPopupSettings: function () |
|
614 { |
|
615 var host = ""; |
|
616 try { |
|
617 host = gBrowser.currentURI.host; |
|
618 } |
|
619 catch (e) { } |
|
620 |
|
621 var bundlePreferences = document.getElementById("bundle_preferences"); |
|
622 var params = { blockVisible : false, |
|
623 sessionVisible : false, |
|
624 allowVisible : true, |
|
625 prefilledHost : host, |
|
626 permissionType : "popup", |
|
627 windowTitle : bundlePreferences.getString("popuppermissionstitle"), |
|
628 introText : bundlePreferences.getString("popuppermissionstext") }; |
|
629 var existingWindow = Services.wm.getMostRecentWindow("Browser:Permissions"); |
|
630 if (existingWindow) { |
|
631 existingWindow.initWithParams(params); |
|
632 existingWindow.focus(); |
|
633 } |
|
634 else |
|
635 window.openDialog("chrome://browser/content/preferences/permissions.xul", |
|
636 "_blank", "resizable,dialog=no,centerscreen", params); |
|
637 }, |
|
638 |
|
639 dontShowMessage: function () |
|
640 { |
|
641 var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage"); |
|
642 gPrefService.setBoolPref("privacy.popups.showBrowserMessage", !showMessage); |
|
643 gBrowser.getNotificationBox().removeCurrentNotification(); |
|
644 } |
|
645 }; |
|
646 |
|
647 const gFormSubmitObserver = { |
|
648 QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]), |
|
649 |
|
650 panel: null, |
|
651 |
|
652 init: function() |
|
653 { |
|
654 this.panel = document.getElementById('invalid-form-popup'); |
|
655 }, |
|
656 |
|
657 notifyInvalidSubmit : function (aFormElement, aInvalidElements) |
|
658 { |
|
659 // We are going to handle invalid form submission attempt by focusing the |
|
660 // first invalid element and show the corresponding validation message in a |
|
661 // panel attached to the element. |
|
662 if (!aInvalidElements.length) { |
|
663 return; |
|
664 } |
|
665 |
|
666 // Don't show the popup if the current tab doesn't contain the invalid form. |
|
667 if (gBrowser.contentDocument != |
|
668 aFormElement.ownerDocument.defaultView.top.document) { |
|
669 return; |
|
670 } |
|
671 |
|
672 let element = aInvalidElements.queryElementAt(0, Ci.nsISupports); |
|
673 |
|
674 if (!(element instanceof HTMLInputElement || |
|
675 element instanceof HTMLTextAreaElement || |
|
676 element instanceof HTMLSelectElement || |
|
677 element instanceof HTMLButtonElement)) { |
|
678 return; |
|
679 } |
|
680 |
|
681 this.panel.firstChild.textContent = element.validationMessage; |
|
682 |
|
683 element.focus(); |
|
684 |
|
685 // If the user interacts with the element and makes it valid or leaves it, |
|
686 // we want to remove the popup. |
|
687 // We could check for clicks but a click is already removing the popup. |
|
688 function blurHandler() { |
|
689 gFormSubmitObserver.panel.hidePopup(); |
|
690 }; |
|
691 function inputHandler(e) { |
|
692 if (e.originalTarget.validity.valid) { |
|
693 gFormSubmitObserver.panel.hidePopup(); |
|
694 } else { |
|
695 // If the element is now invalid for a new reason, we should update the |
|
696 // error message. |
|
697 if (gFormSubmitObserver.panel.firstChild.textContent != |
|
698 e.originalTarget.validationMessage) { |
|
699 gFormSubmitObserver.panel.firstChild.textContent = |
|
700 e.originalTarget.validationMessage; |
|
701 } |
|
702 } |
|
703 }; |
|
704 element.addEventListener("input", inputHandler, false); |
|
705 element.addEventListener("blur", blurHandler, false); |
|
706 |
|
707 // One event to bring them all and in the darkness bind them. |
|
708 this.panel.addEventListener("popuphiding", function onPopupHiding(aEvent) { |
|
709 aEvent.target.removeEventListener("popuphiding", onPopupHiding, false); |
|
710 element.removeEventListener("input", inputHandler, false); |
|
711 element.removeEventListener("blur", blurHandler, false); |
|
712 }, false); |
|
713 |
|
714 this.panel.hidden = false; |
|
715 |
|
716 // We want to show the popup at the middle of checkbox and radio buttons |
|
717 // and where the content begin for the other elements. |
|
718 let offset = 0; |
|
719 let position = ""; |
|
720 |
|
721 if (element.tagName == 'INPUT' && |
|
722 (element.type == 'radio' || element.type == 'checkbox')) { |
|
723 position = "bottomcenter topleft"; |
|
724 } else { |
|
725 let win = element.ownerDocument.defaultView; |
|
726 let style = win.getComputedStyle(element, null); |
|
727 let utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) |
|
728 .getInterface(Components.interfaces.nsIDOMWindowUtils); |
|
729 |
|
730 if (style.direction == 'rtl') { |
|
731 offset = parseInt(style.paddingRight) + parseInt(style.borderRightWidth); |
|
732 } else { |
|
733 offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth); |
|
734 } |
|
735 |
|
736 offset = Math.round(offset * utils.fullZoom); |
|
737 |
|
738 position = "after_start"; |
|
739 } |
|
740 |
|
741 this.panel.openPopup(element, position, offset, 0); |
|
742 } |
|
743 }; |
|
744 |
|
745 var gBrowserInit = { |
|
746 delayedStartupFinished: false, |
|
747 |
|
748 onLoad: function() { |
|
749 gMultiProcessBrowser = |
|
750 window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
751 .getInterface(Ci.nsIWebNavigation) |
|
752 .QueryInterface(Ci.nsILoadContext) |
|
753 .useRemoteTabs; |
|
754 |
|
755 var mustLoadSidebar = false; |
|
756 |
|
757 if (!gMultiProcessBrowser) { |
|
758 // There is a Content:Click message manually sent from content. |
|
759 Cc["@mozilla.org/eventlistenerservice;1"] |
|
760 .getService(Ci.nsIEventListenerService) |
|
761 .addSystemEventListener(gBrowser, "click", contentAreaClick, true); |
|
762 } |
|
763 |
|
764 gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false); |
|
765 |
|
766 // Note that the XBL binding is untrusted |
|
767 gBrowser.addEventListener("PluginBindingAttached", gPluginHandler, true, true); |
|
768 gBrowser.addEventListener("PluginCrashed", gPluginHandler, true); |
|
769 gBrowser.addEventListener("PluginOutdated", gPluginHandler, true); |
|
770 gBrowser.addEventListener("PluginInstantiated", gPluginHandler, true); |
|
771 gBrowser.addEventListener("PluginRemoved", gPluginHandler, true); |
|
772 |
|
773 gBrowser.addEventListener("NewPluginInstalled", gPluginHandler.newPluginInstalled, true); |
|
774 |
|
775 Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false); |
|
776 |
|
777 window.addEventListener("AppCommand", HandleAppCommandEvent, true); |
|
778 |
|
779 // These routines add message listeners. They must run before |
|
780 // loading the frame script to ensure that we don't miss any |
|
781 // message sent between when the frame script is loaded and when |
|
782 // the listener is registered. |
|
783 DOMLinkHandler.init(); |
|
784 gPageStyleMenu.init(); |
|
785 LanguageDetectionListener.init(); |
|
786 |
|
787 messageManager.loadFrameScript("chrome://browser/content/content.js", true); |
|
788 |
|
789 // initialize observers and listeners |
|
790 // and give C++ access to gBrowser |
|
791 XULBrowserWindow.init(); |
|
792 window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
793 .getInterface(nsIWebNavigation) |
|
794 .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner |
|
795 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
796 .getInterface(Ci.nsIXULWindow) |
|
797 .XULBrowserWindow = window.XULBrowserWindow; |
|
798 window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = |
|
799 new nsBrowserAccess(); |
|
800 |
|
801 // hook up UI through progress listener |
|
802 gBrowser.addProgressListener(window.XULBrowserWindow); |
|
803 gBrowser.addTabsProgressListener(window.TabsProgressListener); |
|
804 |
|
805 // setup simple gestures support |
|
806 gGestureSupport.init(true); |
|
807 |
|
808 // setup history swipe animation |
|
809 gHistorySwipeAnimation.init(); |
|
810 |
|
811 if (window.opener && !window.opener.closed && |
|
812 PrivateBrowsingUtils.isWindowPrivate(window) == PrivateBrowsingUtils.isWindowPrivate(window.opener)) { |
|
813 let openerSidebarBox = window.opener.document.getElementById("sidebar-box"); |
|
814 // If the opener had a sidebar, open the same sidebar in our window. |
|
815 // The opener can be the hidden window too, if we're coming from the state |
|
816 // where no windows are open, and the hidden window has no sidebar box. |
|
817 if (openerSidebarBox && !openerSidebarBox.hidden) { |
|
818 let sidebarCmd = openerSidebarBox.getAttribute("sidebarcommand"); |
|
819 let sidebarCmdElem = document.getElementById(sidebarCmd); |
|
820 |
|
821 // dynamically generated sidebars will fail this check. |
|
822 if (sidebarCmdElem) { |
|
823 let sidebarBox = document.getElementById("sidebar-box"); |
|
824 let sidebarTitle = document.getElementById("sidebar-title"); |
|
825 |
|
826 sidebarTitle.setAttribute( |
|
827 "value", window.opener.document.getElementById("sidebar-title").getAttribute("value")); |
|
828 sidebarBox.setAttribute("width", openerSidebarBox.boxObject.width); |
|
829 |
|
830 sidebarBox.setAttribute("sidebarcommand", sidebarCmd); |
|
831 // Note: we're setting 'src' on sidebarBox, which is a <vbox>, not on |
|
832 // the <browser id="sidebar">. This lets us delay the actual load until |
|
833 // delayedStartup(). |
|
834 sidebarBox.setAttribute( |
|
835 "src", window.opener.document.getElementById("sidebar").getAttribute("src")); |
|
836 mustLoadSidebar = true; |
|
837 |
|
838 sidebarBox.hidden = false; |
|
839 document.getElementById("sidebar-splitter").hidden = false; |
|
840 sidebarCmdElem.setAttribute("checked", "true"); |
|
841 } |
|
842 } |
|
843 } |
|
844 else { |
|
845 let box = document.getElementById("sidebar-box"); |
|
846 if (box.hasAttribute("sidebarcommand")) { |
|
847 let commandID = box.getAttribute("sidebarcommand"); |
|
848 if (commandID) { |
|
849 let command = document.getElementById(commandID); |
|
850 if (command) { |
|
851 mustLoadSidebar = true; |
|
852 box.hidden = false; |
|
853 document.getElementById("sidebar-splitter").hidden = false; |
|
854 command.setAttribute("checked", "true"); |
|
855 } |
|
856 else { |
|
857 // Remove the |sidebarcommand| attribute, because the element it |
|
858 // refers to no longer exists, so we should assume this sidebar |
|
859 // panel has been uninstalled. (249883) |
|
860 box.removeAttribute("sidebarcommand"); |
|
861 } |
|
862 } |
|
863 } |
|
864 } |
|
865 |
|
866 // Certain kinds of automigration rely on this notification to complete their |
|
867 // tasks BEFORE the browser window is shown. |
|
868 Services.obs.notifyObservers(null, "browser-window-before-show", ""); |
|
869 |
|
870 // Set a sane starting width/height for all resolutions on new profiles. |
|
871 if (!document.documentElement.hasAttribute("width")) { |
|
872 let defaultWidth; |
|
873 let defaultHeight; |
|
874 |
|
875 // Very small: maximize the window |
|
876 // Portrait : use about full width and 3/4 height, to view entire pages |
|
877 // at once (without being obnoxiously tall) |
|
878 // Widescreen: use about half width, to suggest side-by-side page view |
|
879 // Otherwise : use 3/4 height and width |
|
880 if (screen.availHeight <= 600) { |
|
881 document.documentElement.setAttribute("sizemode", "maximized"); |
|
882 defaultWidth = 610; |
|
883 defaultHeight = 450; |
|
884 } |
|
885 else { |
|
886 if (screen.availWidth <= screen.availHeight) { |
|
887 defaultWidth = screen.availWidth * .9; |
|
888 defaultHeight = screen.availHeight * .75; |
|
889 } |
|
890 else if (screen.availWidth >= 2048) { |
|
891 defaultWidth = (screen.availWidth / 2) - 20; |
|
892 defaultHeight = screen.availHeight - 10; |
|
893 } |
|
894 else { |
|
895 defaultWidth = screen.availWidth * .75; |
|
896 defaultHeight = screen.availHeight * .75; |
|
897 } |
|
898 |
|
899 #if MOZ_WIDGET_GTK == 2 |
|
900 // On X, we're not currently able to account for the size of the window |
|
901 // border. Use 28px as a guess (titlebar + bottom window border) |
|
902 defaultHeight -= 28; |
|
903 #endif |
|
904 } |
|
905 document.documentElement.setAttribute("width", defaultWidth); |
|
906 document.documentElement.setAttribute("height", defaultHeight); |
|
907 } |
|
908 |
|
909 if (!window.toolbar.visible) { |
|
910 // adjust browser UI for popups |
|
911 if (gURLBar) { |
|
912 gURLBar.setAttribute("readonly", "true"); |
|
913 gURLBar.setAttribute("enablehistory", "false"); |
|
914 } |
|
915 goSetCommandEnabled("cmd_newNavigatorTab", false); |
|
916 } |
|
917 |
|
918 // Misc. inits. |
|
919 CombinedStopReload.init(); |
|
920 gPrivateBrowsingUI.init(); |
|
921 TabsInTitlebar.init(); |
|
922 |
|
923 #ifdef XP_WIN |
|
924 if (window.matchMedia("(-moz-os-version: windows-win8)").matches && |
|
925 window.matchMedia("(-moz-windows-default-theme)").matches) { |
|
926 let windowFrameColor = Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {}) |
|
927 .Windows8WindowFrameColor.get(); |
|
928 |
|
929 // Formula from W3C's WCAG 2.0 spec's color ratio and relative luminance, |
|
930 // section 1.3.4, http://www.w3.org/TR/WCAG20/ . |
|
931 windowFrameColor = windowFrameColor.map((color) => { |
|
932 if (color <= 10) { |
|
933 return color / 255 / 12.92; |
|
934 } |
|
935 return Math.pow(((color / 255) + 0.055) / 1.055, 2.4); |
|
936 }); |
|
937 let backgroundLuminance = windowFrameColor[0] * 0.2126 + |
|
938 windowFrameColor[1] * 0.7152 + |
|
939 windowFrameColor[2] * 0.0722; |
|
940 let foregroundLuminance = 0; // Default to black for foreground text. |
|
941 let contrastRatio = (backgroundLuminance + 0.05) / (foregroundLuminance + 0.05); |
|
942 if (contrastRatio < 3) { |
|
943 document.documentElement.setAttribute("darkwindowframe", "true"); |
|
944 } |
|
945 } |
|
946 #endif |
|
947 |
|
948 ToolbarIconColor.init(); |
|
949 |
|
950 // Wait until chrome is painted before executing code not critical to making the window visible |
|
951 this._boundDelayedStartup = this._delayedStartup.bind(this, mustLoadSidebar); |
|
952 window.addEventListener("MozAfterPaint", this._boundDelayedStartup); |
|
953 |
|
954 this._loadHandled = true; |
|
955 }, |
|
956 |
|
957 _cancelDelayedStartup: function () { |
|
958 window.removeEventListener("MozAfterPaint", this._boundDelayedStartup); |
|
959 this._boundDelayedStartup = null; |
|
960 }, |
|
961 |
|
962 _delayedStartup: function(mustLoadSidebar) { |
|
963 let tmp = {}; |
|
964 Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", tmp); |
|
965 let TelemetryTimestamps = tmp.TelemetryTimestamps; |
|
966 TelemetryTimestamps.add("delayedStartupStarted"); |
|
967 |
|
968 this._cancelDelayedStartup(); |
|
969 |
|
970 // We need to set the MozApplicationManifest event listeners up |
|
971 // before we start loading the home pages in case a document has |
|
972 // a "manifest" attribute, in which the MozApplicationManifest event |
|
973 // will be fired. |
|
974 gBrowser.addEventListener("MozApplicationManifest", |
|
975 OfflineApps, false); |
|
976 // listen for offline apps on social |
|
977 let socialBrowser = document.getElementById("social-sidebar-browser"); |
|
978 socialBrowser.addEventListener("MozApplicationManifest", |
|
979 OfflineApps, false); |
|
980 |
|
981 let uriToLoad = this._getUriToLoad(); |
|
982 var isLoadingBlank = isBlankPageURL(uriToLoad); |
|
983 |
|
984 // This pageshow listener needs to be registered before we may call |
|
985 // swapBrowsersAndCloseOther() to receive pageshow events fired by that. |
|
986 gBrowser.addEventListener("pageshow", function(event) { |
|
987 // Filter out events that are not about the document load we are interested in |
|
988 if (content && event.target == content.document) |
|
989 setTimeout(pageShowEventHandlers, 0, event.persisted); |
|
990 }, true); |
|
991 |
|
992 if (uriToLoad && uriToLoad != "about:blank") { |
|
993 if (uriToLoad instanceof Ci.nsISupportsArray) { |
|
994 let count = uriToLoad.Count(); |
|
995 let specs = []; |
|
996 for (let i = 0; i < count; i++) { |
|
997 let urisstring = uriToLoad.GetElementAt(i).QueryInterface(Ci.nsISupportsString); |
|
998 specs.push(urisstring.data); |
|
999 } |
|
1000 |
|
1001 // This function throws for certain malformed URIs, so use exception handling |
|
1002 // so that we don't disrupt startup |
|
1003 try { |
|
1004 gBrowser.loadTabs(specs, false, true); |
|
1005 } catch (e) {} |
|
1006 } |
|
1007 else if (uriToLoad instanceof XULElement) { |
|
1008 // swap the given tab with the default about:blank tab and then close |
|
1009 // the original tab in the other window. |
|
1010 |
|
1011 // Stop the about:blank load |
|
1012 gBrowser.stop(); |
|
1013 // make sure it has a docshell |
|
1014 gBrowser.docShell; |
|
1015 |
|
1016 gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad); |
|
1017 } |
|
1018 // window.arguments[2]: referrer (nsIURI) |
|
1019 // [3]: postData (nsIInputStream) |
|
1020 // [4]: allowThirdPartyFixup (bool) |
|
1021 else if (window.arguments.length >= 3) { |
|
1022 loadURI(uriToLoad, window.arguments[2], window.arguments[3] || null, |
|
1023 window.arguments[4] || false); |
|
1024 window.focus(); |
|
1025 } |
|
1026 // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3. |
|
1027 // Such callers expect that window.arguments[0] is handled as a single URI. |
|
1028 else { |
|
1029 if (uriToLoad == "about:newtab" && |
|
1030 Services.prefs.getBoolPref("browser.newtabpage.enabled")) { |
|
1031 Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true); |
|
1032 } |
|
1033 loadOneOrMoreURIs(uriToLoad); |
|
1034 } |
|
1035 } |
|
1036 |
|
1037 #ifdef MOZ_SAFE_BROWSING |
|
1038 // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008. |
|
1039 setTimeout(function() { SafeBrowsing.init(); }, 2000); |
|
1040 #endif |
|
1041 |
|
1042 Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false); |
|
1043 Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false); |
|
1044 Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false); |
|
1045 Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false); |
|
1046 Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false); |
|
1047 Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false); |
|
1048 Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false); |
|
1049 |
|
1050 BrowserOffline.init(); |
|
1051 OfflineApps.init(); |
|
1052 IndexedDBPromptHelper.init(); |
|
1053 CanvasPermissionPromptHelper.init(); |
|
1054 gFormSubmitObserver.init(); |
|
1055 gRemoteTabsUI.init(); |
|
1056 |
|
1057 // Initialize the full zoom setting. |
|
1058 // We do this before the session restore service gets initialized so we can |
|
1059 // apply full zoom settings to tabs restored by the session restore service. |
|
1060 FullZoom.init(); |
|
1061 PanelUI.init(); |
|
1062 LightweightThemeListener.init(); |
|
1063 WebrtcIndicator.init(); |
|
1064 |
|
1065 // Ensure login manager is up and running. |
|
1066 Services.logins; |
|
1067 |
|
1068 #ifdef MOZ_CRASHREPORTER |
|
1069 if (gMultiProcessBrowser) |
|
1070 TabCrashReporter.init(); |
|
1071 #endif |
|
1072 |
|
1073 if (mustLoadSidebar) { |
|
1074 let sidebar = document.getElementById("sidebar"); |
|
1075 let sidebarBox = document.getElementById("sidebar-box"); |
|
1076 sidebar.setAttribute("src", sidebarBox.getAttribute("src")); |
|
1077 } |
|
1078 |
|
1079 UpdateUrlbarSearchSplitterState(); |
|
1080 |
|
1081 if (!isLoadingBlank || !focusAndSelectUrlBar()) |
|
1082 gBrowser.selectedBrowser.focus(); |
|
1083 |
|
1084 // Set up Sanitize Item |
|
1085 this._initializeSanitizer(); |
|
1086 |
|
1087 // Enable/Disable auto-hide tabbar |
|
1088 gBrowser.tabContainer.updateVisibility(); |
|
1089 |
|
1090 BookmarkingUI.init(); |
|
1091 |
|
1092 gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false); |
|
1093 |
|
1094 var homeButton = document.getElementById("home-button"); |
|
1095 gHomeButton.updateTooltip(homeButton); |
|
1096 gHomeButton.updatePersonalToolbarStyle(homeButton); |
|
1097 |
|
1098 // BiDi UI |
|
1099 gBidiUI = isBidiEnabled(); |
|
1100 if (gBidiUI) { |
|
1101 document.getElementById("documentDirection-separator").hidden = false; |
|
1102 document.getElementById("documentDirection-swap").hidden = false; |
|
1103 document.getElementById("textfieldDirection-separator").hidden = false; |
|
1104 document.getElementById("textfieldDirection-swap").hidden = false; |
|
1105 } |
|
1106 |
|
1107 // Setup click-and-hold gestures access to the session history |
|
1108 // menus if global click-and-hold isn't turned on |
|
1109 if (!getBoolPref("ui.click_hold_context_menus", false)) |
|
1110 SetClickAndHoldHandlers(); |
|
1111 |
|
1112 let NP = {}; |
|
1113 Cu.import("resource:///modules/NetworkPrioritizer.jsm", NP); |
|
1114 NP.trackBrowserWindow(window); |
|
1115 |
|
1116 PlacesToolbarHelper.init(); |
|
1117 |
|
1118 ctrlTab.readPref(); |
|
1119 gPrefService.addObserver(ctrlTab.prefName, ctrlTab, false); |
|
1120 |
|
1121 // Initialize the download manager some time after the app starts so that |
|
1122 // auto-resume downloads begin (such as after crashing or quitting with |
|
1123 // active downloads) and speeds up the first-load of the download manager UI. |
|
1124 // If the user manually opens the download manager before the timeout, the |
|
1125 // downloads will start right away, and initializing again won't hurt. |
|
1126 setTimeout(function() { |
|
1127 try { |
|
1128 Cu.import("resource:///modules/DownloadsCommon.jsm", {}) |
|
1129 .DownloadsCommon.initializeAllDataLinks(); |
|
1130 Cu.import("resource:///modules/DownloadsTaskbar.jsm", {}) |
|
1131 .DownloadsTaskbar.registerIndicator(window); |
|
1132 } catch (ex) { |
|
1133 Cu.reportError(ex); |
|
1134 } |
|
1135 }, 10000); |
|
1136 |
|
1137 // The object handling the downloads indicator is also initialized here in the |
|
1138 // delayed startup function, but the actual indicator element is not loaded |
|
1139 // unless there are downloads to be displayed. |
|
1140 DownloadsButton.initializeIndicator(); |
|
1141 |
|
1142 #ifndef XP_MACOSX |
|
1143 updateEditUIVisibility(); |
|
1144 let placesContext = document.getElementById("placesContext"); |
|
1145 placesContext.addEventListener("popupshowing", updateEditUIVisibility, false); |
|
1146 placesContext.addEventListener("popuphiding", updateEditUIVisibility, false); |
|
1147 #endif |
|
1148 |
|
1149 gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true); |
|
1150 gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true); |
|
1151 gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true); |
|
1152 |
|
1153 if (Win7Features) |
|
1154 Win7Features.onOpenWindow(); |
|
1155 |
|
1156 // called when we go into full screen, even if initiated by a web page script |
|
1157 window.addEventListener("fullscreen", onFullScreen, true); |
|
1158 |
|
1159 // Called when we enter DOM full-screen mode. Note we can already be in browser |
|
1160 // full-screen mode when we enter DOM full-screen mode. |
|
1161 window.addEventListener("MozEnteredDomFullscreen", onMozEnteredDomFullscreen, true); |
|
1162 |
|
1163 if (window.fullScreen) |
|
1164 onFullScreen(); |
|
1165 if (document.mozFullScreen) |
|
1166 onMozEnteredDomFullscreen(); |
|
1167 |
|
1168 #ifdef MOZ_SERVICES_SYNC |
|
1169 // initialize the sync UI |
|
1170 gSyncUI.init(); |
|
1171 gFxAccounts.init(); |
|
1172 #endif |
|
1173 |
|
1174 #ifdef MOZ_DATA_REPORTING |
|
1175 gDataNotificationInfoBar.init(); |
|
1176 #endif |
|
1177 |
|
1178 gBrowserThumbnails.init(); |
|
1179 |
|
1180 // Add Devtools menuitems and listeners |
|
1181 gDevToolsBrowser.registerBrowserWindow(window); |
|
1182 |
|
1183 window.addEventListener("mousemove", MousePosTracker, false); |
|
1184 window.addEventListener("dragover", MousePosTracker, false); |
|
1185 |
|
1186 gNavToolbox.addEventListener("customizationstarting", CustomizationHandler); |
|
1187 gNavToolbox.addEventListener("customizationchange", CustomizationHandler); |
|
1188 gNavToolbox.addEventListener("customizationending", CustomizationHandler); |
|
1189 |
|
1190 // End startup crash tracking after a delay to catch crashes while restoring |
|
1191 // tabs and to postpone saving the pref to disk. |
|
1192 try { |
|
1193 const startupCrashEndDelay = 30 * 1000; |
|
1194 setTimeout(Services.startup.trackStartupCrashEnd, startupCrashEndDelay); |
|
1195 } catch (ex) { |
|
1196 Cu.reportError("Could not end startup crash tracking: " + ex); |
|
1197 } |
|
1198 |
|
1199 if (typeof WindowsPrefSync !== 'undefined') { |
|
1200 // Pulls in Metro controlled prefs and pushes out Desktop controlled prefs |
|
1201 WindowsPrefSync.init(); |
|
1202 } |
|
1203 |
|
1204 SessionStore.promiseInitialized.then(() => { |
|
1205 // Bail out if the window has been closed in the meantime. |
|
1206 if (window.closed) { |
|
1207 return; |
|
1208 } |
|
1209 |
|
1210 // Enable the Restore Last Session command if needed |
|
1211 RestoreLastSessionObserver.init(); |
|
1212 |
|
1213 SocialUI.init(); |
|
1214 TabView.init(); |
|
1215 |
|
1216 setTimeout(function () { BrowserChromeTest.markAsReady(); }, 0); |
|
1217 }); |
|
1218 this.delayedStartupFinished = true; |
|
1219 |
|
1220 Services.obs.notifyObservers(window, "browser-delayed-startup-finished", ""); |
|
1221 TelemetryTimestamps.add("delayedStartupFinished"); |
|
1222 }, |
|
1223 |
|
1224 // Returns the URI(s) to load at startup. |
|
1225 _getUriToLoad: function () { |
|
1226 // window.arguments[0]: URI to load (string), or an nsISupportsArray of |
|
1227 // nsISupportsStrings to load, or a xul:tab of |
|
1228 // a tabbrowser, which will be replaced by this |
|
1229 // window (for this case, all other arguments are |
|
1230 // ignored). |
|
1231 if (!window.arguments || !window.arguments[0]) |
|
1232 return null; |
|
1233 |
|
1234 let uri = window.arguments[0]; |
|
1235 let sessionStartup = Cc["@mozilla.org/browser/sessionstartup;1"] |
|
1236 .getService(Ci.nsISessionStartup); |
|
1237 let defaultArgs = Cc["@mozilla.org/browser/clh;1"] |
|
1238 .getService(Ci.nsIBrowserHandler) |
|
1239 .defaultArgs; |
|
1240 |
|
1241 // If the given URI matches defaultArgs (the default homepage) we want |
|
1242 // to block its load if we're going to restore a session anyway. |
|
1243 if (uri == defaultArgs && sessionStartup.willOverrideHomepage) |
|
1244 return null; |
|
1245 |
|
1246 return uri; |
|
1247 }, |
|
1248 |
|
1249 onUnload: function() { |
|
1250 // In certain scenarios it's possible for unload to be fired before onload, |
|
1251 // (e.g. if the window is being closed after browser.js loads but before the |
|
1252 // load completes). In that case, there's nothing to do here. |
|
1253 if (!this._loadHandled) |
|
1254 return; |
|
1255 |
|
1256 gDevToolsBrowser.forgetBrowserWindow(window); |
|
1257 |
|
1258 let desc = Object.getOwnPropertyDescriptor(window, "DeveloperToolbar"); |
|
1259 if (desc && !desc.get) { |
|
1260 DeveloperToolbar.destroy(); |
|
1261 } |
|
1262 |
|
1263 // First clean up services initialized in gBrowserInit.onLoad (or those whose |
|
1264 // uninit methods don't depend on the services having been initialized). |
|
1265 |
|
1266 CombinedStopReload.uninit(); |
|
1267 |
|
1268 gGestureSupport.init(false); |
|
1269 |
|
1270 gHistorySwipeAnimation.uninit(); |
|
1271 |
|
1272 FullScreen.cleanup(); |
|
1273 |
|
1274 #ifdef MOZ_SERVICES_SYNC |
|
1275 gFxAccounts.uninit(); |
|
1276 #endif |
|
1277 |
|
1278 Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed"); |
|
1279 |
|
1280 try { |
|
1281 gBrowser.removeProgressListener(window.XULBrowserWindow); |
|
1282 gBrowser.removeTabsProgressListener(window.TabsProgressListener); |
|
1283 } catch (ex) { |
|
1284 } |
|
1285 |
|
1286 PlacesToolbarHelper.uninit(); |
|
1287 |
|
1288 BookmarkingUI.uninit(); |
|
1289 |
|
1290 TabsInTitlebar.uninit(); |
|
1291 |
|
1292 ToolbarIconColor.uninit(); |
|
1293 |
|
1294 var enumerator = Services.wm.getEnumerator(null); |
|
1295 enumerator.getNext(); |
|
1296 if (!enumerator.hasMoreElements()) { |
|
1297 document.persist("sidebar-box", "sidebarcommand"); |
|
1298 document.persist("sidebar-box", "width"); |
|
1299 document.persist("sidebar-box", "src"); |
|
1300 document.persist("sidebar-title", "value"); |
|
1301 } |
|
1302 |
|
1303 // Now either cancel delayedStartup, or clean up the services initialized from |
|
1304 // it. |
|
1305 if (this._boundDelayedStartup) { |
|
1306 this._cancelDelayedStartup(); |
|
1307 } else { |
|
1308 if (Win7Features) |
|
1309 Win7Features.onCloseWindow(); |
|
1310 |
|
1311 gPrefService.removeObserver(ctrlTab.prefName, ctrlTab); |
|
1312 ctrlTab.uninit(); |
|
1313 TabView.uninit(); |
|
1314 SocialUI.uninit(); |
|
1315 gBrowserThumbnails.uninit(); |
|
1316 FullZoom.destroy(); |
|
1317 |
|
1318 Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history"); |
|
1319 Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled"); |
|
1320 Services.obs.removeObserver(gXPInstallObserver, "addon-install-started"); |
|
1321 Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked"); |
|
1322 Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed"); |
|
1323 Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete"); |
|
1324 Services.obs.removeObserver(gFormSubmitObserver, "invalidformsubmit"); |
|
1325 |
|
1326 try { |
|
1327 gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton); |
|
1328 } catch (ex) { |
|
1329 Cu.reportError(ex); |
|
1330 } |
|
1331 |
|
1332 if (typeof WindowsPrefSync !== 'undefined') { |
|
1333 WindowsPrefSync.uninit(); |
|
1334 } |
|
1335 |
|
1336 BrowserOffline.uninit(); |
|
1337 OfflineApps.uninit(); |
|
1338 IndexedDBPromptHelper.uninit(); |
|
1339 CanvasPermissionPromptHelper.uninit(); |
|
1340 LightweightThemeListener.uninit(); |
|
1341 PanelUI.uninit(); |
|
1342 } |
|
1343 |
|
1344 // Final window teardown, do this last. |
|
1345 window.XULBrowserWindow = null; |
|
1346 window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
1347 .getInterface(Ci.nsIWebNavigation) |
|
1348 .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner |
|
1349 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
1350 .getInterface(Ci.nsIXULWindow) |
|
1351 .XULBrowserWindow = null; |
|
1352 window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = null; |
|
1353 }, |
|
1354 |
|
1355 #ifdef XP_MACOSX |
|
1356 // nonBrowserWindowStartup(), nonBrowserWindowDelayedStartup(), and |
|
1357 // nonBrowserWindowShutdown() are used for non-browser windows in |
|
1358 // macBrowserOverlay |
|
1359 nonBrowserWindowStartup: function() { |
|
1360 // Disable inappropriate commands / submenus |
|
1361 var disabledItems = ['Browser:SavePage', |
|
1362 'Browser:SendLink', 'cmd_pageSetup', 'cmd_print', 'cmd_find', 'cmd_findAgain', |
|
1363 'viewToolbarsMenu', 'viewSidebarMenuMenu', 'Browser:Reload', |
|
1364 'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu', 'View:PageSource', 'View:FullScreen', |
|
1365 'viewHistorySidebar', 'Browser:AddBookmarkAs', 'Browser:BookmarkAllTabs', |
|
1366 'View:PageInfo', 'Browser:ToggleTabView']; |
|
1367 var element; |
|
1368 |
|
1369 for (let disabledItem of disabledItems) { |
|
1370 element = document.getElementById(disabledItem); |
|
1371 if (element) |
|
1372 element.setAttribute("disabled", "true"); |
|
1373 } |
|
1374 |
|
1375 // If no windows are active (i.e. we're the hidden window), disable the close, minimize |
|
1376 // and zoom menu commands as well |
|
1377 if (window.location.href == "chrome://browser/content/hiddenWindow.xul") { |
|
1378 var hiddenWindowDisabledItems = ['cmd_close', 'minimizeWindow', 'zoomWindow']; |
|
1379 for (let hiddenWindowDisabledItem of hiddenWindowDisabledItems) { |
|
1380 element = document.getElementById(hiddenWindowDisabledItem); |
|
1381 if (element) |
|
1382 element.setAttribute("disabled", "true"); |
|
1383 } |
|
1384 |
|
1385 // also hide the window-list separator |
|
1386 element = document.getElementById("sep-window-list"); |
|
1387 element.setAttribute("hidden", "true"); |
|
1388 |
|
1389 // Setup the dock menu. |
|
1390 let dockMenuElement = document.getElementById("menu_mac_dockmenu"); |
|
1391 if (dockMenuElement != null) { |
|
1392 let nativeMenu = Cc["@mozilla.org/widget/standalonenativemenu;1"] |
|
1393 .createInstance(Ci.nsIStandaloneNativeMenu); |
|
1394 |
|
1395 try { |
|
1396 nativeMenu.init(dockMenuElement); |
|
1397 |
|
1398 let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"] |
|
1399 .getService(Ci.nsIMacDockSupport); |
|
1400 dockSupport.dockMenu = nativeMenu; |
|
1401 } |
|
1402 catch (e) { |
|
1403 } |
|
1404 } |
|
1405 } |
|
1406 |
|
1407 if (PrivateBrowsingUtils.permanentPrivateBrowsing) { |
|
1408 document.getElementById("macDockMenuNewWindow").hidden = true; |
|
1409 } |
|
1410 |
|
1411 this._delayedStartupTimeoutId = setTimeout(this.nonBrowserWindowDelayedStartup.bind(this), 0); |
|
1412 }, |
|
1413 |
|
1414 nonBrowserWindowDelayedStartup: function() { |
|
1415 this._delayedStartupTimeoutId = null; |
|
1416 |
|
1417 // initialise the offline listener |
|
1418 BrowserOffline.init(); |
|
1419 |
|
1420 // Set up Sanitize Item |
|
1421 this._initializeSanitizer(); |
|
1422 |
|
1423 // initialize the private browsing UI |
|
1424 gPrivateBrowsingUI.init(); |
|
1425 |
|
1426 #ifdef MOZ_SERVICES_SYNC |
|
1427 // initialize the sync UI |
|
1428 gSyncUI.init(); |
|
1429 #endif |
|
1430 }, |
|
1431 |
|
1432 nonBrowserWindowShutdown: function() { |
|
1433 // If nonBrowserWindowDelayedStartup hasn't run yet, we have no work to do - |
|
1434 // just cancel the pending timeout and return; |
|
1435 if (this._delayedStartupTimeoutId) { |
|
1436 clearTimeout(this._delayedStartupTimeoutId); |
|
1437 return; |
|
1438 } |
|
1439 |
|
1440 BrowserOffline.uninit(); |
|
1441 }, |
|
1442 #endif |
|
1443 |
|
1444 _initializeSanitizer: function() { |
|
1445 const kDidSanitizeDomain = "privacy.sanitize.didShutdownSanitize"; |
|
1446 if (gPrefService.prefHasUserValue(kDidSanitizeDomain)) { |
|
1447 gPrefService.clearUserPref(kDidSanitizeDomain); |
|
1448 // We need to persist this preference change, since we want to |
|
1449 // check it at next app start even if the browser exits abruptly |
|
1450 gPrefService.savePrefFile(null); |
|
1451 } |
|
1452 |
|
1453 /** |
|
1454 * Migrate Firefox 3.0 privacy.item prefs under one of these conditions: |
|
1455 * |
|
1456 * a) User has customized any privacy.item prefs |
|
1457 * b) privacy.sanitize.sanitizeOnShutdown is set |
|
1458 */ |
|
1459 if (!gPrefService.getBoolPref("privacy.sanitize.migrateFx3Prefs")) { |
|
1460 let itemBranch = gPrefService.getBranch("privacy.item."); |
|
1461 let itemArray = itemBranch.getChildList(""); |
|
1462 |
|
1463 // See if any privacy.item prefs are set |
|
1464 let doMigrate = itemArray.some(function (name) itemBranch.prefHasUserValue(name)); |
|
1465 // Or if sanitizeOnShutdown is set |
|
1466 if (!doMigrate) |
|
1467 doMigrate = gPrefService.getBoolPref("privacy.sanitize.sanitizeOnShutdown"); |
|
1468 |
|
1469 if (doMigrate) { |
|
1470 let cpdBranch = gPrefService.getBranch("privacy.cpd."); |
|
1471 let clearOnShutdownBranch = gPrefService.getBranch("privacy.clearOnShutdown."); |
|
1472 for (let name of itemArray) { |
|
1473 try { |
|
1474 // don't migrate password or offlineApps clearing in the CRH dialog since |
|
1475 // there's no UI for those anymore. They default to false. bug 497656 |
|
1476 if (name != "passwords" && name != "offlineApps") |
|
1477 cpdBranch.setBoolPref(name, itemBranch.getBoolPref(name)); |
|
1478 clearOnShutdownBranch.setBoolPref(name, itemBranch.getBoolPref(name)); |
|
1479 } |
|
1480 catch(e) { |
|
1481 Cu.reportError("Exception thrown during privacy pref migration: " + e); |
|
1482 } |
|
1483 } |
|
1484 } |
|
1485 |
|
1486 gPrefService.setBoolPref("privacy.sanitize.migrateFx3Prefs", true); |
|
1487 } |
|
1488 }, |
|
1489 } |
|
1490 |
|
1491 |
|
1492 /* Legacy global init functions */ |
|
1493 var BrowserStartup = gBrowserInit.onLoad.bind(gBrowserInit); |
|
1494 var BrowserShutdown = gBrowserInit.onUnload.bind(gBrowserInit); |
|
1495 #ifdef XP_MACOSX |
|
1496 var nonBrowserWindowStartup = gBrowserInit.nonBrowserWindowStartup.bind(gBrowserInit); |
|
1497 var nonBrowserWindowDelayedStartup = gBrowserInit.nonBrowserWindowDelayedStartup.bind(gBrowserInit); |
|
1498 var nonBrowserWindowShutdown = gBrowserInit.nonBrowserWindowShutdown.bind(gBrowserInit); |
|
1499 #endif |
|
1500 |
|
1501 function HandleAppCommandEvent(evt) { |
|
1502 switch (evt.command) { |
|
1503 case "Back": |
|
1504 BrowserBack(); |
|
1505 break; |
|
1506 case "Forward": |
|
1507 BrowserForward(); |
|
1508 break; |
|
1509 case "Reload": |
|
1510 BrowserReloadSkipCache(); |
|
1511 break; |
|
1512 case "Stop": |
|
1513 if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") |
|
1514 BrowserStop(); |
|
1515 break; |
|
1516 case "Search": |
|
1517 BrowserSearch.webSearch(); |
|
1518 break; |
|
1519 case "Bookmarks": |
|
1520 toggleSidebar('viewBookmarksSidebar'); |
|
1521 break; |
|
1522 case "Home": |
|
1523 BrowserHome(); |
|
1524 break; |
|
1525 case "New": |
|
1526 BrowserOpenTab(); |
|
1527 break; |
|
1528 case "Close": |
|
1529 BrowserCloseTabOrWindow(); |
|
1530 break; |
|
1531 case "Find": |
|
1532 gFindBar.onFindCommand(); |
|
1533 break; |
|
1534 case "Help": |
|
1535 openHelpLink('firefox-help'); |
|
1536 break; |
|
1537 case "Open": |
|
1538 BrowserOpenFileWindow(); |
|
1539 break; |
|
1540 case "Print": |
|
1541 PrintUtils.print(); |
|
1542 break; |
|
1543 case "Save": |
|
1544 saveDocument(window.content.document); |
|
1545 break; |
|
1546 case "SendMail": |
|
1547 MailIntegration.sendLinkForWindow(window.content); |
|
1548 break; |
|
1549 default: |
|
1550 return; |
|
1551 } |
|
1552 evt.stopPropagation(); |
|
1553 evt.preventDefault(); |
|
1554 } |
|
1555 |
|
1556 function gotoHistoryIndex(aEvent) { |
|
1557 let index = aEvent.target.getAttribute("index"); |
|
1558 if (!index) |
|
1559 return false; |
|
1560 |
|
1561 let where = whereToOpenLink(aEvent); |
|
1562 |
|
1563 if (where == "current") { |
|
1564 // Normal click. Go there in the current tab and update session history. |
|
1565 |
|
1566 try { |
|
1567 gBrowser.gotoIndex(index); |
|
1568 } |
|
1569 catch(ex) { |
|
1570 return false; |
|
1571 } |
|
1572 return true; |
|
1573 } |
|
1574 // Modified click. Go there in a new tab/window. |
|
1575 |
|
1576 duplicateTabIn(gBrowser.selectedTab, where, index - gBrowser.sessionHistory.index); |
|
1577 return true; |
|
1578 } |
|
1579 |
|
1580 function BrowserForward(aEvent) { |
|
1581 let where = whereToOpenLink(aEvent, false, true); |
|
1582 |
|
1583 if (where == "current") { |
|
1584 try { |
|
1585 gBrowser.goForward(); |
|
1586 } |
|
1587 catch(ex) { |
|
1588 } |
|
1589 } |
|
1590 else { |
|
1591 duplicateTabIn(gBrowser.selectedTab, where, 1); |
|
1592 } |
|
1593 } |
|
1594 |
|
1595 function BrowserBack(aEvent) { |
|
1596 let where = whereToOpenLink(aEvent, false, true); |
|
1597 |
|
1598 if (where == "current") { |
|
1599 try { |
|
1600 gBrowser.goBack(); |
|
1601 } |
|
1602 catch(ex) { |
|
1603 } |
|
1604 } |
|
1605 else { |
|
1606 duplicateTabIn(gBrowser.selectedTab, where, -1); |
|
1607 } |
|
1608 } |
|
1609 |
|
1610 function BrowserHandleBackspace() |
|
1611 { |
|
1612 switch (gPrefService.getIntPref("browser.backspace_action")) { |
|
1613 case 0: |
|
1614 BrowserBack(); |
|
1615 break; |
|
1616 case 1: |
|
1617 goDoCommand("cmd_scrollPageUp"); |
|
1618 break; |
|
1619 } |
|
1620 } |
|
1621 |
|
1622 function BrowserHandleShiftBackspace() |
|
1623 { |
|
1624 switch (gPrefService.getIntPref("browser.backspace_action")) { |
|
1625 case 0: |
|
1626 BrowserForward(); |
|
1627 break; |
|
1628 case 1: |
|
1629 goDoCommand("cmd_scrollPageDown"); |
|
1630 break; |
|
1631 } |
|
1632 } |
|
1633 |
|
1634 function BrowserStop() { |
|
1635 const stopFlags = nsIWebNavigation.STOP_ALL; |
|
1636 gBrowser.webNavigation.stop(stopFlags); |
|
1637 } |
|
1638 |
|
1639 function BrowserReloadOrDuplicate(aEvent) { |
|
1640 var backgroundTabModifier = aEvent.button == 1 || |
|
1641 #ifdef XP_MACOSX |
|
1642 aEvent.metaKey; |
|
1643 #else |
|
1644 aEvent.ctrlKey; |
|
1645 #endif |
|
1646 if (aEvent.shiftKey && !backgroundTabModifier) { |
|
1647 BrowserReloadSkipCache(); |
|
1648 return; |
|
1649 } |
|
1650 |
|
1651 let where = whereToOpenLink(aEvent, false, true); |
|
1652 if (where == "current") |
|
1653 BrowserReload(); |
|
1654 else |
|
1655 duplicateTabIn(gBrowser.selectedTab, where); |
|
1656 } |
|
1657 |
|
1658 function BrowserReload() { |
|
1659 const reloadFlags = nsIWebNavigation.LOAD_FLAGS_NONE; |
|
1660 BrowserReloadWithFlags(reloadFlags); |
|
1661 } |
|
1662 |
|
1663 function BrowserReloadSkipCache() { |
|
1664 // Bypass proxy and cache. |
|
1665 const reloadFlags = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE; |
|
1666 BrowserReloadWithFlags(reloadFlags); |
|
1667 } |
|
1668 |
|
1669 var BrowserHome = BrowserGoHome; |
|
1670 function BrowserGoHome(aEvent) { |
|
1671 if (aEvent && "button" in aEvent && |
|
1672 aEvent.button == 2) // right-click: do nothing |
|
1673 return; |
|
1674 |
|
1675 var homePage = gHomeButton.getHomePage(); |
|
1676 var where = whereToOpenLink(aEvent, false, true); |
|
1677 var urls; |
|
1678 |
|
1679 // Home page should open in a new tab when current tab is an app tab |
|
1680 if (where == "current" && |
|
1681 gBrowser && |
|
1682 gBrowser.selectedTab.pinned) |
|
1683 where = "tab"; |
|
1684 |
|
1685 // openUILinkIn in utilityOverlay.js doesn't handle loading multiple pages |
|
1686 switch (where) { |
|
1687 case "current": |
|
1688 loadOneOrMoreURIs(homePage); |
|
1689 break; |
|
1690 case "tabshifted": |
|
1691 case "tab": |
|
1692 urls = homePage.split("|"); |
|
1693 var loadInBackground = getBoolPref("browser.tabs.loadBookmarksInBackground", false); |
|
1694 gBrowser.loadTabs(urls, loadInBackground); |
|
1695 break; |
|
1696 case "window": |
|
1697 OpenBrowserWindow(); |
|
1698 break; |
|
1699 } |
|
1700 } |
|
1701 |
|
1702 function loadOneOrMoreURIs(aURIString) |
|
1703 { |
|
1704 #ifdef XP_MACOSX |
|
1705 // we're not a browser window, pass the URI string to a new browser window |
|
1706 if (window.location.href != getBrowserURL()) |
|
1707 { |
|
1708 window.openDialog(getBrowserURL(), "_blank", "all,dialog=no", aURIString); |
|
1709 return; |
|
1710 } |
|
1711 #endif |
|
1712 // This function throws for certain malformed URIs, so use exception handling |
|
1713 // so that we don't disrupt startup |
|
1714 try { |
|
1715 gBrowser.loadTabs(aURIString.split("|"), false, true); |
|
1716 } |
|
1717 catch (e) { |
|
1718 } |
|
1719 } |
|
1720 |
|
1721 function focusAndSelectUrlBar() { |
|
1722 if (gURLBar) { |
|
1723 if (window.fullScreen) |
|
1724 FullScreen.mouseoverToggle(true); |
|
1725 |
|
1726 gURLBar.select(); |
|
1727 if (document.activeElement == gURLBar.inputField) |
|
1728 return true; |
|
1729 } |
|
1730 return false; |
|
1731 } |
|
1732 |
|
1733 function openLocation() { |
|
1734 if (focusAndSelectUrlBar()) |
|
1735 return; |
|
1736 |
|
1737 #ifdef XP_MACOSX |
|
1738 if (window.location.href != getBrowserURL()) { |
|
1739 var win = getTopWin(); |
|
1740 if (win) { |
|
1741 // If there's an open browser window, it should handle this command |
|
1742 win.focus() |
|
1743 win.openLocation(); |
|
1744 } |
|
1745 else { |
|
1746 // If there are no open browser windows, open a new one |
|
1747 window.openDialog("chrome://browser/content/", "_blank", |
|
1748 "chrome,all,dialog=no", BROWSER_NEW_TAB_URL); |
|
1749 } |
|
1750 } |
|
1751 #endif |
|
1752 } |
|
1753 |
|
1754 function BrowserOpenTab() |
|
1755 { |
|
1756 openUILinkIn(BROWSER_NEW_TAB_URL, "tab"); |
|
1757 } |
|
1758 |
|
1759 /* Called from the openLocation dialog. This allows that dialog to instruct |
|
1760 its opener to open a new window and then step completely out of the way. |
|
1761 Anything less byzantine is causing horrible crashes, rather believably, |
|
1762 though oddly only on Linux. */ |
|
1763 function delayedOpenWindow(chrome, flags, href, postData) |
|
1764 { |
|
1765 // The other way to use setTimeout, |
|
1766 // setTimeout(openDialog, 10, chrome, "_blank", flags, url), |
|
1767 // doesn't work here. The extra "magic" extra argument setTimeout adds to |
|
1768 // the callback function would confuse gBrowserInit.onLoad() by making |
|
1769 // window.arguments[1] be an integer instead of null. |
|
1770 setTimeout(function() { openDialog(chrome, "_blank", flags, href, null, null, postData); }, 10); |
|
1771 } |
|
1772 |
|
1773 /* Required because the tab needs time to set up its content viewers and get the load of |
|
1774 the URI kicked off before becoming the active content area. */ |
|
1775 function delayedOpenTab(aUrl, aReferrer, aCharset, aPostData, aAllowThirdPartyFixup) |
|
1776 { |
|
1777 gBrowser.loadOneTab(aUrl, { |
|
1778 referrerURI: aReferrer, |
|
1779 charset: aCharset, |
|
1780 postData: aPostData, |
|
1781 inBackground: false, |
|
1782 allowThirdPartyFixup: aAllowThirdPartyFixup}); |
|
1783 } |
|
1784 |
|
1785 var gLastOpenDirectory = { |
|
1786 _lastDir: null, |
|
1787 get path() { |
|
1788 if (!this._lastDir || !this._lastDir.exists()) { |
|
1789 try { |
|
1790 this._lastDir = gPrefService.getComplexValue("browser.open.lastDir", |
|
1791 Ci.nsILocalFile); |
|
1792 if (!this._lastDir.exists()) |
|
1793 this._lastDir = null; |
|
1794 } |
|
1795 catch(e) {} |
|
1796 } |
|
1797 return this._lastDir; |
|
1798 }, |
|
1799 set path(val) { |
|
1800 try { |
|
1801 if (!val || !val.isDirectory()) |
|
1802 return; |
|
1803 } catch(e) { |
|
1804 return; |
|
1805 } |
|
1806 this._lastDir = val.clone(); |
|
1807 |
|
1808 // Don't save the last open directory pref inside the Private Browsing mode |
|
1809 if (!PrivateBrowsingUtils.isWindowPrivate(window)) |
|
1810 gPrefService.setComplexValue("browser.open.lastDir", Ci.nsILocalFile, |
|
1811 this._lastDir); |
|
1812 }, |
|
1813 reset: function() { |
|
1814 this._lastDir = null; |
|
1815 } |
|
1816 }; |
|
1817 |
|
1818 function BrowserOpenFileWindow() |
|
1819 { |
|
1820 // Get filepicker component. |
|
1821 try { |
|
1822 const nsIFilePicker = Ci.nsIFilePicker; |
|
1823 let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); |
|
1824 let fpCallback = function fpCallback_done(aResult) { |
|
1825 if (aResult == nsIFilePicker.returnOK) { |
|
1826 try { |
|
1827 if (fp.file) { |
|
1828 gLastOpenDirectory.path = |
|
1829 fp.file.parent.QueryInterface(Ci.nsILocalFile); |
|
1830 } |
|
1831 } catch (ex) { |
|
1832 } |
|
1833 openUILinkIn(fp.fileURL.spec, "current"); |
|
1834 } |
|
1835 }; |
|
1836 |
|
1837 fp.init(window, gNavigatorBundle.getString("openFile"), |
|
1838 nsIFilePicker.modeOpen); |
|
1839 fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText | |
|
1840 nsIFilePicker.filterImages | nsIFilePicker.filterXML | |
|
1841 nsIFilePicker.filterHTML); |
|
1842 fp.displayDirectory = gLastOpenDirectory.path; |
|
1843 fp.open(fpCallback); |
|
1844 } catch (ex) { |
|
1845 } |
|
1846 } |
|
1847 |
|
1848 function BrowserCloseTabOrWindow() { |
|
1849 #ifdef XP_MACOSX |
|
1850 // If we're not a browser window, just close the window |
|
1851 if (window.location.href != getBrowserURL()) { |
|
1852 closeWindow(true); |
|
1853 return; |
|
1854 } |
|
1855 #endif |
|
1856 |
|
1857 // If the current tab is the last one, this will close the window. |
|
1858 gBrowser.removeCurrentTab({animate: true}); |
|
1859 } |
|
1860 |
|
1861 function BrowserTryToCloseWindow() |
|
1862 { |
|
1863 if (WindowIsClosing()) |
|
1864 window.close(); // WindowIsClosing does all the necessary checks |
|
1865 } |
|
1866 |
|
1867 function loadURI(uri, referrer, postData, allowThirdPartyFixup) { |
|
1868 if (postData === undefined) |
|
1869 postData = null; |
|
1870 |
|
1871 var flags = nsIWebNavigation.LOAD_FLAGS_NONE; |
|
1872 if (allowThirdPartyFixup) { |
|
1873 flags |= nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; |
|
1874 flags |= nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; |
|
1875 } |
|
1876 |
|
1877 try { |
|
1878 gBrowser.loadURIWithFlags(uri, flags, referrer, null, postData); |
|
1879 } catch (e) {} |
|
1880 } |
|
1881 |
|
1882 function getShortcutOrURIAndPostData(aURL, aCallback) { |
|
1883 let mayInheritPrincipal = false; |
|
1884 let postData = null; |
|
1885 let shortcutURL = null; |
|
1886 let keyword = aURL; |
|
1887 let param = ""; |
|
1888 |
|
1889 let offset = aURL.indexOf(" "); |
|
1890 if (offset > 0) { |
|
1891 keyword = aURL.substr(0, offset); |
|
1892 param = aURL.substr(offset + 1); |
|
1893 } |
|
1894 |
|
1895 let engine = Services.search.getEngineByAlias(keyword); |
|
1896 if (engine) { |
|
1897 let submission = engine.getSubmission(param); |
|
1898 postData = submission.postData; |
|
1899 aCallback({ postData: submission.postData, url: submission.uri.spec, |
|
1900 mayInheritPrincipal: mayInheritPrincipal }); |
|
1901 return; |
|
1902 } |
|
1903 |
|
1904 [shortcutURL, postData] = |
|
1905 PlacesUtils.getURLAndPostDataForKeyword(keyword); |
|
1906 |
|
1907 if (!shortcutURL) { |
|
1908 aCallback({ postData: postData, url: aURL, |
|
1909 mayInheritPrincipal: mayInheritPrincipal }); |
|
1910 return; |
|
1911 } |
|
1912 |
|
1913 let escapedPostData = ""; |
|
1914 if (postData) |
|
1915 escapedPostData = unescape(postData); |
|
1916 |
|
1917 if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) { |
|
1918 let charset = ""; |
|
1919 const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/; |
|
1920 let matches = shortcutURL.match(re); |
|
1921 |
|
1922 let continueOperation = function () { |
|
1923 // encodeURIComponent produces UTF-8, and cannot be used for other charsets. |
|
1924 // escape() works in those cases, but it doesn't uri-encode +, @, and /. |
|
1925 // Therefore we need to manually replace these ASCII characters by their |
|
1926 // encodeURIComponent result, to match the behavior of nsEscape() with |
|
1927 // url_XPAlphas |
|
1928 let encodedParam = ""; |
|
1929 if (charset && charset != "UTF-8") |
|
1930 encodedParam = escape(convertFromUnicode(charset, param)). |
|
1931 replace(/[+@\/]+/g, encodeURIComponent); |
|
1932 else // Default charset is UTF-8 |
|
1933 encodedParam = encodeURIComponent(param); |
|
1934 |
|
1935 shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param); |
|
1936 |
|
1937 if (/%s/i.test(escapedPostData)) // POST keyword |
|
1938 postData = getPostDataStream(escapedPostData, param, encodedParam, |
|
1939 "application/x-www-form-urlencoded"); |
|
1940 |
|
1941 // This URL came from a bookmark, so it's safe to let it inherit the current |
|
1942 // document's principal. |
|
1943 mayInheritPrincipal = true; |
|
1944 |
|
1945 aCallback({ postData: postData, url: shortcutURL, |
|
1946 mayInheritPrincipal: mayInheritPrincipal }); |
|
1947 } |
|
1948 |
|
1949 if (matches) { |
|
1950 [, shortcutURL, charset] = matches; |
|
1951 continueOperation(); |
|
1952 } else { |
|
1953 // Try to get the saved character-set. |
|
1954 // makeURI throws if URI is invalid. |
|
1955 // Will return an empty string if character-set is not found. |
|
1956 try { |
|
1957 PlacesUtils.getCharsetForURI(makeURI(shortcutURL)) |
|
1958 .then(c => { charset = c; continueOperation(); }); |
|
1959 } catch (ex) { |
|
1960 continueOperation(); |
|
1961 } |
|
1962 } |
|
1963 } |
|
1964 else if (param) { |
|
1965 // This keyword doesn't take a parameter, but one was provided. Just return |
|
1966 // the original URL. |
|
1967 postData = null; |
|
1968 |
|
1969 aCallback({ postData: postData, url: aURL, |
|
1970 mayInheritPrincipal: mayInheritPrincipal }); |
|
1971 } else { |
|
1972 // This URL came from a bookmark, so it's safe to let it inherit the current |
|
1973 // document's principal. |
|
1974 mayInheritPrincipal = true; |
|
1975 |
|
1976 aCallback({ postData: postData, url: shortcutURL, |
|
1977 mayInheritPrincipal: mayInheritPrincipal }); |
|
1978 } |
|
1979 } |
|
1980 |
|
1981 function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) { |
|
1982 var dataStream = Cc["@mozilla.org/io/string-input-stream;1"]. |
|
1983 createInstance(Ci.nsIStringInputStream); |
|
1984 aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword); |
|
1985 dataStream.data = aStringData; |
|
1986 |
|
1987 var mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"]. |
|
1988 createInstance(Ci.nsIMIMEInputStream); |
|
1989 mimeStream.addHeader("Content-Type", aType); |
|
1990 mimeStream.addContentLength = true; |
|
1991 mimeStream.setData(dataStream); |
|
1992 return mimeStream.QueryInterface(Ci.nsIInputStream); |
|
1993 } |
|
1994 |
|
1995 function getLoadContext() { |
|
1996 return window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
1997 .getInterface(Ci.nsIWebNavigation) |
|
1998 .QueryInterface(Ci.nsILoadContext); |
|
1999 } |
|
2000 |
|
2001 function readFromClipboard() |
|
2002 { |
|
2003 var url; |
|
2004 |
|
2005 try { |
|
2006 // Create transferable that will transfer the text. |
|
2007 var trans = Components.classes["@mozilla.org/widget/transferable;1"] |
|
2008 .createInstance(Components.interfaces.nsITransferable); |
|
2009 trans.init(getLoadContext()); |
|
2010 |
|
2011 trans.addDataFlavor("text/unicode"); |
|
2012 |
|
2013 // If available, use selection clipboard, otherwise global one |
|
2014 if (Services.clipboard.supportsSelectionClipboard()) |
|
2015 Services.clipboard.getData(trans, Services.clipboard.kSelectionClipboard); |
|
2016 else |
|
2017 Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard); |
|
2018 |
|
2019 var data = {}; |
|
2020 var dataLen = {}; |
|
2021 trans.getTransferData("text/unicode", data, dataLen); |
|
2022 |
|
2023 if (data) { |
|
2024 data = data.value.QueryInterface(Components.interfaces.nsISupportsString); |
|
2025 url = data.data.substring(0, dataLen.value / 2); |
|
2026 } |
|
2027 } catch (ex) { |
|
2028 } |
|
2029 |
|
2030 return url; |
|
2031 } |
|
2032 |
|
2033 function BrowserViewSourceOfDocument(aDocument) |
|
2034 { |
|
2035 var pageCookie; |
|
2036 var webNav; |
|
2037 |
|
2038 // Get the document charset |
|
2039 var docCharset = "charset=" + aDocument.characterSet; |
|
2040 |
|
2041 // Get the nsIWebNavigation associated with the document |
|
2042 try { |
|
2043 var win; |
|
2044 var ifRequestor; |
|
2045 |
|
2046 // Get the DOMWindow for the requested document. If the DOMWindow |
|
2047 // cannot be found, then just use the content window... |
|
2048 // |
|
2049 // XXX: This is a bit of a hack... |
|
2050 win = aDocument.defaultView; |
|
2051 if (win == window) { |
|
2052 win = content; |
|
2053 } |
|
2054 ifRequestor = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor); |
|
2055 |
|
2056 webNav = ifRequestor.getInterface(nsIWebNavigation); |
|
2057 } catch(err) { |
|
2058 // If nsIWebNavigation cannot be found, just get the one for the whole |
|
2059 // window... |
|
2060 webNav = gBrowser.webNavigation; |
|
2061 } |
|
2062 // |
|
2063 // Get the 'PageDescriptor' for the current document. This allows the |
|
2064 // view-source to access the cached copy of the content rather than |
|
2065 // refetching it from the network... |
|
2066 // |
|
2067 try{ |
|
2068 var PageLoader = webNav.QueryInterface(Components.interfaces.nsIWebPageDescriptor); |
|
2069 |
|
2070 pageCookie = PageLoader.currentDescriptor; |
|
2071 } catch(err) { |
|
2072 // If no page descriptor is available, just use the view-source URL... |
|
2073 } |
|
2074 |
|
2075 top.gViewSourceUtils.viewSource(webNav.currentURI.spec, pageCookie, aDocument); |
|
2076 } |
|
2077 |
|
2078 // doc - document to use for source, or null for this window's document |
|
2079 // initialTab - name of the initial tab to display, or null for the first tab |
|
2080 // imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted |
|
2081 function BrowserPageInfo(doc, initialTab, imageElement) { |
|
2082 var args = {doc: doc, initialTab: initialTab, imageElement: imageElement}; |
|
2083 var windows = Services.wm.getEnumerator("Browser:page-info"); |
|
2084 |
|
2085 var documentURL = doc ? doc.location : window.content.document.location; |
|
2086 |
|
2087 // Check for windows matching the url |
|
2088 while (windows.hasMoreElements()) { |
|
2089 var currentWindow = windows.getNext(); |
|
2090 if (currentWindow.closed) { |
|
2091 continue; |
|
2092 } |
|
2093 if (currentWindow.document.documentElement.getAttribute("relatedUrl") == documentURL) { |
|
2094 currentWindow.focus(); |
|
2095 currentWindow.resetPageInfo(args); |
|
2096 return currentWindow; |
|
2097 } |
|
2098 } |
|
2099 |
|
2100 // We didn't find a matching window, so open a new one. |
|
2101 return openDialog("chrome://browser/content/pageinfo/pageInfo.xul", "", |
|
2102 "chrome,toolbar,dialog=no,resizable", args); |
|
2103 } |
|
2104 |
|
2105 function URLBarSetURI(aURI) { |
|
2106 var value = gBrowser.userTypedValue; |
|
2107 var valid = false; |
|
2108 |
|
2109 if (value == null) { |
|
2110 let uri = aURI || gBrowser.currentURI; |
|
2111 // Strip off "wyciwyg://" and passwords for the location bar |
|
2112 try { |
|
2113 uri = Services.uriFixup.createExposableURI(uri); |
|
2114 } catch (e) {} |
|
2115 |
|
2116 // Replace initial page URIs with an empty string |
|
2117 // only if there's no opener (bug 370555). |
|
2118 // Bug 863515 - Make content.opener checks work in electrolysis. |
|
2119 if (gInitialPages.indexOf(uri.spec) != -1) |
|
2120 value = !gMultiProcessBrowser && content.opener ? uri.spec : ""; |
|
2121 else |
|
2122 value = losslessDecodeURI(uri); |
|
2123 |
|
2124 valid = !isBlankPageURL(uri.spec); |
|
2125 } |
|
2126 |
|
2127 gURLBar.value = value; |
|
2128 gURLBar.valueIsTyped = !valid; |
|
2129 SetPageProxyState(valid ? "valid" : "invalid"); |
|
2130 } |
|
2131 |
|
2132 function losslessDecodeURI(aURI) { |
|
2133 var value = aURI.spec; |
|
2134 // Try to decode as UTF-8 if there's no encoding sequence that we would break. |
|
2135 if (!/%25(?:3B|2F|3F|3A|40|26|3D|2B|24|2C|23)/i.test(value)) |
|
2136 try { |
|
2137 value = decodeURI(value) |
|
2138 // 1. decodeURI decodes %25 to %, which creates unintended |
|
2139 // encoding sequences. Re-encode it, unless it's part of |
|
2140 // a sequence that survived decodeURI, i.e. one for: |
|
2141 // ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#' |
|
2142 // (RFC 3987 section 3.2) |
|
2143 // 2. Re-encode whitespace so that it doesn't get eaten away |
|
2144 // by the location bar (bug 410726). |
|
2145 .replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|[\r\n\t]/ig, |
|
2146 encodeURIComponent); |
|
2147 } catch (e) {} |
|
2148 |
|
2149 // Encode invisible characters (C0/C1 control characters, U+007F [DEL], |
|
2150 // U+00A0 [no-break space], line and paragraph separator, |
|
2151 // object replacement character) (bug 452979, bug 909264) |
|
2152 value = value.replace(/[\u0000-\u001f\u007f-\u00a0\u2028\u2029\ufffc]/g, |
|
2153 encodeURIComponent); |
|
2154 |
|
2155 // Encode default ignorable characters (bug 546013) |
|
2156 // except ZWNJ (U+200C) and ZWJ (U+200D) (bug 582186). |
|
2157 // This includes all bidirectional formatting characters. |
|
2158 // (RFC 3987 sections 3.2 and 4.1 paragraph 6) |
|
2159 value = value.replace(/[\u00ad\u034f\u061c\u115f-\u1160\u17b4-\u17b5\u180b-\u180d\u200b\u200e-\u200f\u202a-\u202e\u2060-\u206f\u3164\ufe00-\ufe0f\ufeff\uffa0\ufff0-\ufff8]|\ud834[\udd73-\udd7a]|[\udb40-\udb43][\udc00-\udfff]/g, |
|
2160 encodeURIComponent); |
|
2161 return value; |
|
2162 } |
|
2163 |
|
2164 function UpdateUrlbarSearchSplitterState() |
|
2165 { |
|
2166 var splitter = document.getElementById("urlbar-search-splitter"); |
|
2167 var urlbar = document.getElementById("urlbar-container"); |
|
2168 var searchbar = document.getElementById("search-container"); |
|
2169 |
|
2170 if (document.documentElement.getAttribute("customizing") == "true") { |
|
2171 if (splitter) { |
|
2172 splitter.remove(); |
|
2173 } |
|
2174 return; |
|
2175 } |
|
2176 |
|
2177 // If the splitter is already in the right place, we don't need to do anything: |
|
2178 if (splitter && |
|
2179 ((splitter.nextSibling == searchbar && splitter.previousSibling == urlbar) || |
|
2180 (splitter.nextSibling == urlbar && splitter.previousSibling == searchbar))) { |
|
2181 return; |
|
2182 } |
|
2183 |
|
2184 var ibefore = null; |
|
2185 if (urlbar && searchbar) { |
|
2186 if (urlbar.nextSibling == searchbar) |
|
2187 ibefore = searchbar; |
|
2188 else if (searchbar.nextSibling == urlbar) |
|
2189 ibefore = urlbar; |
|
2190 } |
|
2191 |
|
2192 if (ibefore) { |
|
2193 if (!splitter) { |
|
2194 splitter = document.createElement("splitter"); |
|
2195 splitter.id = "urlbar-search-splitter"; |
|
2196 splitter.setAttribute("resizebefore", "flex"); |
|
2197 splitter.setAttribute("resizeafter", "flex"); |
|
2198 splitter.setAttribute("skipintoolbarset", "true"); |
|
2199 splitter.setAttribute("overflows", "false"); |
|
2200 splitter.className = "chromeclass-toolbar-additional"; |
|
2201 } |
|
2202 urlbar.parentNode.insertBefore(splitter, ibefore); |
|
2203 } else if (splitter) |
|
2204 splitter.parentNode.removeChild(splitter); |
|
2205 } |
|
2206 |
|
2207 function UpdatePageProxyState() |
|
2208 { |
|
2209 if (gURLBar && gURLBar.value != gLastValidURLStr) |
|
2210 SetPageProxyState("invalid"); |
|
2211 } |
|
2212 |
|
2213 function SetPageProxyState(aState) |
|
2214 { |
|
2215 BookmarkingUI.onPageProxyStateChanged(aState); |
|
2216 |
|
2217 if (!gURLBar) |
|
2218 return; |
|
2219 |
|
2220 if (!gProxyFavIcon) |
|
2221 gProxyFavIcon = document.getElementById("page-proxy-favicon"); |
|
2222 |
|
2223 gURLBar.setAttribute("pageproxystate", aState); |
|
2224 gProxyFavIcon.setAttribute("pageproxystate", aState); |
|
2225 |
|
2226 // the page proxy state is set to valid via OnLocationChange, which |
|
2227 // gets called when we switch tabs. |
|
2228 if (aState == "valid") { |
|
2229 gLastValidURLStr = gURLBar.value; |
|
2230 gURLBar.addEventListener("input", UpdatePageProxyState, false); |
|
2231 } else if (aState == "invalid") { |
|
2232 gURLBar.removeEventListener("input", UpdatePageProxyState, false); |
|
2233 } |
|
2234 } |
|
2235 |
|
2236 function PageProxyClickHandler(aEvent) |
|
2237 { |
|
2238 if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste")) |
|
2239 middleMousePaste(aEvent); |
|
2240 } |
|
2241 |
|
2242 /** |
|
2243 * Handle command events bubbling up from error page content |
|
2244 * or from about:newtab |
|
2245 */ |
|
2246 let BrowserOnClick = { |
|
2247 handleEvent: function BrowserOnClick_handleEvent(aEvent) { |
|
2248 if (!aEvent.isTrusted || // Don't trust synthetic events |
|
2249 aEvent.button == 2) { |
|
2250 return; |
|
2251 } |
|
2252 |
|
2253 let originalTarget = aEvent.originalTarget; |
|
2254 let ownerDoc = originalTarget.ownerDocument; |
|
2255 |
|
2256 // If the event came from an ssl error page, it is probably either the "Add |
|
2257 // Exception…" or "Get me out of here!" button |
|
2258 if (ownerDoc.documentURI.startsWith("about:certerror")) { |
|
2259 this.onAboutCertError(originalTarget, ownerDoc); |
|
2260 } |
|
2261 else if (ownerDoc.documentURI.startsWith("about:blocked")) { |
|
2262 this.onAboutBlocked(originalTarget, ownerDoc); |
|
2263 } |
|
2264 else if (ownerDoc.documentURI.startsWith("about:neterror")) { |
|
2265 this.onAboutNetError(originalTarget, ownerDoc); |
|
2266 } |
|
2267 else if (gMultiProcessBrowser && |
|
2268 ownerDoc.documentURI.toLowerCase() == "about:newtab") { |
|
2269 this.onE10sAboutNewTab(aEvent, ownerDoc); |
|
2270 } |
|
2271 else if (ownerDoc.documentURI.startsWith("about:tabcrashed")) { |
|
2272 this.onAboutTabCrashed(aEvent, ownerDoc); |
|
2273 } |
|
2274 }, |
|
2275 |
|
2276 onAboutCertError: function BrowserOnClick_onAboutCertError(aTargetElm, aOwnerDoc) { |
|
2277 let elmId = aTargetElm.getAttribute("id"); |
|
2278 let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI"); |
|
2279 let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView); |
|
2280 |
|
2281 switch (elmId) { |
|
2282 case "exceptionDialogButton": |
|
2283 if (isTopFrame) { |
|
2284 secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION); |
|
2285 } |
|
2286 let params = { exceptionAdded : false }; |
|
2287 |
|
2288 try { |
|
2289 switch (Services.prefs.getIntPref("browser.ssl_override_behavior")) { |
|
2290 case 2 : // Pre-fetch & pre-populate |
|
2291 params.prefetchCert = true; |
|
2292 case 1 : // Pre-populate |
|
2293 params.location = aOwnerDoc.location.href; |
|
2294 } |
|
2295 } catch (e) { |
|
2296 Components.utils.reportError("Couldn't get ssl_override pref: " + e); |
|
2297 } |
|
2298 |
|
2299 window.openDialog('chrome://pippki/content/exceptionDialog.xul', |
|
2300 '','chrome,centerscreen,modal', params); |
|
2301 |
|
2302 // If the user added the exception cert, attempt to reload the page |
|
2303 if (params.exceptionAdded) { |
|
2304 aOwnerDoc.location.reload(); |
|
2305 } |
|
2306 break; |
|
2307 |
|
2308 case "getMeOutOfHereButton": |
|
2309 if (isTopFrame) { |
|
2310 secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_GET_ME_OUT_OF_HERE); |
|
2311 } |
|
2312 getMeOutOfHere(); |
|
2313 break; |
|
2314 |
|
2315 case "technicalContent": |
|
2316 if (isTopFrame) { |
|
2317 secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_TECHNICAL_DETAILS); |
|
2318 } |
|
2319 break; |
|
2320 |
|
2321 case "expertContent": |
|
2322 if (isTopFrame) { |
|
2323 secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_UNDERSTAND_RISKS); |
|
2324 } |
|
2325 break; |
|
2326 |
|
2327 } |
|
2328 }, |
|
2329 |
|
2330 onAboutBlocked: function BrowserOnClick_onAboutBlocked(aTargetElm, aOwnerDoc) { |
|
2331 let elmId = aTargetElm.getAttribute("id"); |
|
2332 let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI"); |
|
2333 |
|
2334 // The event came from a button on a malware/phishing block page |
|
2335 // First check whether it's malware or phishing, so that we can |
|
2336 // use the right strings/links |
|
2337 let isMalware = /e=malwareBlocked/.test(aOwnerDoc.documentURI); |
|
2338 let bucketName = isMalware ? "WARNING_MALWARE_PAGE_":"WARNING_PHISHING_PAGE_"; |
|
2339 let nsISecTel = Ci.nsISecurityUITelemetry; |
|
2340 let isIframe = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView); |
|
2341 bucketName += isIframe ? "TOP_" : "FRAME_"; |
|
2342 |
|
2343 switch (elmId) { |
|
2344 case "getMeOutButton": |
|
2345 secHistogram.add(nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]); |
|
2346 getMeOutOfHere(); |
|
2347 break; |
|
2348 |
|
2349 case "reportButton": |
|
2350 // This is the "Why is this site blocked" button. For malware, |
|
2351 // we can fetch a site-specific report, for phishing, we redirect |
|
2352 // to the generic page describing phishing protection. |
|
2353 |
|
2354 // We log even if malware/phishing info URL couldn't be found: |
|
2355 // the measurement is for how many users clicked the WHY BLOCKED button |
|
2356 secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]); |
|
2357 |
|
2358 if (isMalware) { |
|
2359 // Get the stop badware "why is this blocked" report url, |
|
2360 // append the current url, and go there. |
|
2361 try { |
|
2362 let reportURL = formatURL("browser.safebrowsing.malware.reportURL", true); |
|
2363 reportURL += aOwnerDoc.location.href; |
|
2364 content.location = reportURL; |
|
2365 } catch (e) { |
|
2366 Components.utils.reportError("Couldn't get malware report URL: " + e); |
|
2367 } |
|
2368 } |
|
2369 else { // It's a phishing site, not malware |
|
2370 openHelpLink("phishing-malware", false, "current"); |
|
2371 } |
|
2372 break; |
|
2373 |
|
2374 case "ignoreWarningButton": |
|
2375 secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]); |
|
2376 this.ignoreWarningButton(isMalware); |
|
2377 break; |
|
2378 } |
|
2379 }, |
|
2380 |
|
2381 /** |
|
2382 * This functions prevents navigation from happening directly through the <a> |
|
2383 * link in about:newtab (which is loaded in the parent and therefore would load |
|
2384 * the next page also in the parent) and instructs the browser to open the url |
|
2385 * in the current tab which will make it update the remoteness of the tab. |
|
2386 */ |
|
2387 onE10sAboutNewTab: function(aEvent, aOwnerDoc) { |
|
2388 let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView); |
|
2389 if (!isTopFrame || aEvent.button != 0) { |
|
2390 return; |
|
2391 } |
|
2392 |
|
2393 let anchorTarget = aEvent.originalTarget.parentNode; |
|
2394 |
|
2395 if (anchorTarget instanceof HTMLAnchorElement && |
|
2396 anchorTarget.classList.contains("newtab-link")) { |
|
2397 aEvent.preventDefault(); |
|
2398 openUILinkIn(anchorTarget.href, "current"); |
|
2399 } |
|
2400 }, |
|
2401 |
|
2402 /** |
|
2403 * The about:tabcrashed can't do window.reload() because that |
|
2404 * would reload the page but not use a remote browser. |
|
2405 */ |
|
2406 onAboutTabCrashed: function(aEvent, aOwnerDoc) { |
|
2407 let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView); |
|
2408 if (!isTopFrame) { |
|
2409 return; |
|
2410 } |
|
2411 |
|
2412 let button = aEvent.originalTarget; |
|
2413 if (button.id == "tryAgain") { |
|
2414 #ifdef MOZ_CRASHREPORTER |
|
2415 if (aOwnerDoc.getElementById("checkSendReport").checked) { |
|
2416 let browser = gBrowser.getBrowserForDocument(aOwnerDoc); |
|
2417 TabCrashReporter.submitCrashReport(browser); |
|
2418 } |
|
2419 #endif |
|
2420 openUILinkIn(button.getAttribute("url"), "current"); |
|
2421 } |
|
2422 }, |
|
2423 |
|
2424 ignoreWarningButton: function BrowserOnClick_ignoreWarningButton(aIsMalware) { |
|
2425 // Allow users to override and continue through to the site, |
|
2426 // but add a notify bar as a reminder, so that they don't lose |
|
2427 // track after, e.g., tab switching. |
|
2428 gBrowser.loadURIWithFlags(content.location.href, |
|
2429 nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER, |
|
2430 null, null, null); |
|
2431 |
|
2432 Services.perms.add(makeURI(content.location.href), "safe-browsing", |
|
2433 Ci.nsIPermissionManager.ALLOW_ACTION, |
|
2434 Ci.nsIPermissionManager.EXPIRE_SESSION); |
|
2435 |
|
2436 let buttons = [{ |
|
2437 label: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.label"), |
|
2438 accessKey: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.accessKey"), |
|
2439 callback: function() { getMeOutOfHere(); } |
|
2440 }]; |
|
2441 |
|
2442 let title; |
|
2443 if (aIsMalware) { |
|
2444 title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite"); |
|
2445 buttons[1] = { |
|
2446 label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"), |
|
2447 accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"), |
|
2448 callback: function() { |
|
2449 openUILinkIn(gSafeBrowsing.getReportURL('MalwareError'), 'tab'); |
|
2450 } |
|
2451 }; |
|
2452 } else { |
|
2453 title = gNavigatorBundle.getString("safebrowsing.reportedWebForgery"); |
|
2454 buttons[1] = { |
|
2455 label: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.label"), |
|
2456 accessKey: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.accessKey"), |
|
2457 callback: function() { |
|
2458 openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab'); |
|
2459 } |
|
2460 }; |
|
2461 } |
|
2462 |
|
2463 let notificationBox = gBrowser.getNotificationBox(); |
|
2464 let value = "blocked-badware-page"; |
|
2465 |
|
2466 let previousNotification = notificationBox.getNotificationWithValue(value); |
|
2467 if (previousNotification) { |
|
2468 notificationBox.removeNotification(previousNotification); |
|
2469 } |
|
2470 |
|
2471 let notification = notificationBox.appendNotification( |
|
2472 title, |
|
2473 value, |
|
2474 "chrome://global/skin/icons/blacklist_favicon.png", |
|
2475 notificationBox.PRIORITY_CRITICAL_HIGH, |
|
2476 buttons |
|
2477 ); |
|
2478 // Persist the notification until the user removes so it |
|
2479 // doesn't get removed on redirects. |
|
2480 notification.persistence = -1; |
|
2481 }, |
|
2482 |
|
2483 onAboutNetError: function BrowserOnClick_onAboutNetError(aTargetElm, aOwnerDoc) { |
|
2484 let elmId = aTargetElm.getAttribute("id"); |
|
2485 if (elmId != "errorTryAgain" || !/e=netOffline/.test(aOwnerDoc.documentURI)) |
|
2486 return; |
|
2487 Services.io.offline = false; |
|
2488 }, |
|
2489 }; |
|
2490 |
|
2491 /** |
|
2492 * Re-direct the browser to a known-safe page. This function is |
|
2493 * used when, for example, the user browses to a known malware page |
|
2494 * and is presented with about:blocked. The "Get me out of here!" |
|
2495 * button should take the user to the default start page so that even |
|
2496 * when their own homepage is infected, we can get them somewhere safe. |
|
2497 */ |
|
2498 function getMeOutOfHere() { |
|
2499 // Get the start page from the *default* pref branch, not the user's |
|
2500 var prefs = Services.prefs.getDefaultBranch(null); |
|
2501 var url = BROWSER_NEW_TAB_URL; |
|
2502 try { |
|
2503 url = prefs.getComplexValue("browser.startup.homepage", |
|
2504 Ci.nsIPrefLocalizedString).data; |
|
2505 // If url is a pipe-delimited set of pages, just take the first one. |
|
2506 if (url.contains("|")) |
|
2507 url = url.split("|")[0]; |
|
2508 } catch(e) { |
|
2509 Components.utils.reportError("Couldn't get homepage pref: " + e); |
|
2510 } |
|
2511 content.location = url; |
|
2512 } |
|
2513 |
|
2514 function BrowserFullScreen() |
|
2515 { |
|
2516 window.fullScreen = !window.fullScreen; |
|
2517 } |
|
2518 |
|
2519 function _checkDefaultAndSwitchToMetro() { |
|
2520 #ifdef HAVE_SHELL_SERVICE |
|
2521 #ifdef XP_WIN |
|
2522 #ifdef MOZ_METRO |
|
2523 let shell = Components.classes["@mozilla.org/browser/shell-service;1"]. |
|
2524 getService(Components.interfaces.nsIShellService); |
|
2525 let isDefault = shell.isDefaultBrowser(false, false); |
|
2526 |
|
2527 if (isDefault) { |
|
2528 let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]. |
|
2529 getService(Components.interfaces.nsIAppStartup); |
|
2530 |
|
2531 Services.prefs.setBoolPref('browser.sessionstore.resume_session_once', true); |
|
2532 |
|
2533 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"] |
|
2534 .createInstance(Ci.nsISupportsPRBool); |
|
2535 Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); |
|
2536 |
|
2537 if (!cancelQuit.data) { |
|
2538 appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit | |
|
2539 Components.interfaces.nsIAppStartup.eRestartTouchEnvironment); |
|
2540 } |
|
2541 return true; |
|
2542 } |
|
2543 return false; |
|
2544 #endif |
|
2545 #endif |
|
2546 #endif |
|
2547 } |
|
2548 |
|
2549 function SwitchToMetro() { |
|
2550 #ifdef HAVE_SHELL_SERVICE |
|
2551 #ifdef XP_WIN |
|
2552 #ifdef MOZ_METRO |
|
2553 if (this._checkDefaultAndSwitchToMetro()) { |
|
2554 return; |
|
2555 } |
|
2556 |
|
2557 let shell = Components.classes["@mozilla.org/browser/shell-service;1"]. |
|
2558 getService(Components.interfaces.nsIShellService); |
|
2559 |
|
2560 shell.setDefaultBrowser(false, false); |
|
2561 |
|
2562 let intervalID = window.setInterval(this._checkDefaultAndSwitchToMetro, 1000); |
|
2563 window.setTimeout(function() { window.clearInterval(intervalID); }, 10000); |
|
2564 #endif |
|
2565 #endif |
|
2566 #endif |
|
2567 } |
|
2568 |
|
2569 function onFullScreen(event) { |
|
2570 FullScreen.toggle(event); |
|
2571 } |
|
2572 |
|
2573 function onMozEnteredDomFullscreen(event) { |
|
2574 FullScreen.enterDomFullscreen(event); |
|
2575 } |
|
2576 |
|
2577 function getWebNavigation() |
|
2578 { |
|
2579 return gBrowser.webNavigation; |
|
2580 } |
|
2581 |
|
2582 function BrowserReloadWithFlags(reloadFlags) { |
|
2583 let url = gBrowser.currentURI.spec; |
|
2584 if (gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, url)) { |
|
2585 // If the remoteness has changed, the new browser doesn't have any |
|
2586 // information of what was loaded before, so we need to load the previous |
|
2587 // URL again. |
|
2588 gBrowser.loadURIWithFlags(url, reloadFlags); |
|
2589 return; |
|
2590 } |
|
2591 |
|
2592 /* First, we'll try to use the session history object to reload so |
|
2593 * that framesets are handled properly. If we're in a special |
|
2594 * window (such as view-source) that has no session history, fall |
|
2595 * back on using the web navigation's reload method. |
|
2596 */ |
|
2597 |
|
2598 var webNav = gBrowser.webNavigation; |
|
2599 try { |
|
2600 var sh = webNav.sessionHistory; |
|
2601 if (sh) |
|
2602 webNav = sh.QueryInterface(nsIWebNavigation); |
|
2603 } catch (e) { |
|
2604 } |
|
2605 |
|
2606 try { |
|
2607 webNav.reload(reloadFlags); |
|
2608 } catch (e) { |
|
2609 } |
|
2610 } |
|
2611 |
|
2612 var PrintPreviewListener = { |
|
2613 _printPreviewTab: null, |
|
2614 _tabBeforePrintPreview: null, |
|
2615 |
|
2616 getPrintPreviewBrowser: function () { |
|
2617 if (!this._printPreviewTab) { |
|
2618 this._tabBeforePrintPreview = gBrowser.selectedTab; |
|
2619 this._printPreviewTab = gBrowser.loadOneTab("about:blank", |
|
2620 { inBackground: false }); |
|
2621 gBrowser.selectedTab = this._printPreviewTab; |
|
2622 } |
|
2623 return gBrowser.getBrowserForTab(this._printPreviewTab); |
|
2624 }, |
|
2625 getSourceBrowser: function () { |
|
2626 return this._tabBeforePrintPreview ? |
|
2627 this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser; |
|
2628 }, |
|
2629 getNavToolbox: function () { |
|
2630 return gNavToolbox; |
|
2631 }, |
|
2632 onEnter: function () { |
|
2633 gInPrintPreviewMode = true; |
|
2634 this._toggleAffectedChrome(); |
|
2635 }, |
|
2636 onExit: function () { |
|
2637 gBrowser.selectedTab = this._tabBeforePrintPreview; |
|
2638 this._tabBeforePrintPreview = null; |
|
2639 gInPrintPreviewMode = false; |
|
2640 this._toggleAffectedChrome(); |
|
2641 gBrowser.removeTab(this._printPreviewTab); |
|
2642 this._printPreviewTab = null; |
|
2643 }, |
|
2644 _toggleAffectedChrome: function () { |
|
2645 gNavToolbox.collapsed = gInPrintPreviewMode; |
|
2646 |
|
2647 if (gInPrintPreviewMode) |
|
2648 this._hideChrome(); |
|
2649 else |
|
2650 this._showChrome(); |
|
2651 |
|
2652 if (this._chromeState.sidebarOpen) |
|
2653 toggleSidebar(this._sidebarCommand); |
|
2654 |
|
2655 TabsInTitlebar.allowedBy("print-preview", !gInPrintPreviewMode); |
|
2656 }, |
|
2657 _hideChrome: function () { |
|
2658 this._chromeState = {}; |
|
2659 |
|
2660 var sidebar = document.getElementById("sidebar-box"); |
|
2661 this._chromeState.sidebarOpen = !sidebar.hidden; |
|
2662 this._sidebarCommand = sidebar.getAttribute("sidebarcommand"); |
|
2663 |
|
2664 var notificationBox = gBrowser.getNotificationBox(); |
|
2665 this._chromeState.notificationsOpen = !notificationBox.notificationsHidden; |
|
2666 notificationBox.notificationsHidden = true; |
|
2667 |
|
2668 document.getElementById("sidebar").setAttribute("src", "about:blank"); |
|
2669 gBrowser.updateWindowResizers(); |
|
2670 |
|
2671 this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden; |
|
2672 if (gFindBarInitialized) |
|
2673 gFindBar.close(); |
|
2674 |
|
2675 var globalNotificationBox = document.getElementById("global-notificationbox"); |
|
2676 this._chromeState.globalNotificationsOpen = !globalNotificationBox.notificationsHidden; |
|
2677 globalNotificationBox.notificationsHidden = true; |
|
2678 |
|
2679 this._chromeState.syncNotificationsOpen = false; |
|
2680 var syncNotifications = document.getElementById("sync-notifications"); |
|
2681 if (syncNotifications) { |
|
2682 this._chromeState.syncNotificationsOpen = !syncNotifications.notificationsHidden; |
|
2683 syncNotifications.notificationsHidden = true; |
|
2684 } |
|
2685 }, |
|
2686 _showChrome: function () { |
|
2687 if (this._chromeState.notificationsOpen) |
|
2688 gBrowser.getNotificationBox().notificationsHidden = false; |
|
2689 |
|
2690 if (this._chromeState.findOpen) |
|
2691 gFindBar.open(); |
|
2692 |
|
2693 if (this._chromeState.globalNotificationsOpen) |
|
2694 document.getElementById("global-notificationbox").notificationsHidden = false; |
|
2695 |
|
2696 if (this._chromeState.syncNotificationsOpen) |
|
2697 document.getElementById("sync-notifications").notificationsHidden = false; |
|
2698 } |
|
2699 } |
|
2700 |
|
2701 function getMarkupDocumentViewer() |
|
2702 { |
|
2703 return gBrowser.markupDocumentViewer; |
|
2704 } |
|
2705 |
|
2706 // This function is obsolete. Newer code should use <tooltip page="true"/> instead. |
|
2707 function FillInHTMLTooltip(tipElement) |
|
2708 { |
|
2709 document.getElementById("aHTMLTooltip").fillInPageTooltip(tipElement); |
|
2710 } |
|
2711 |
|
2712 var browserDragAndDrop = { |
|
2713 canDropLink: function (aEvent) Services.droppedLinkHandler.canDropLink(aEvent, true), |
|
2714 |
|
2715 dragOver: function (aEvent) |
|
2716 { |
|
2717 if (this.canDropLink(aEvent)) { |
|
2718 aEvent.preventDefault(); |
|
2719 } |
|
2720 }, |
|
2721 |
|
2722 drop: function (aEvent, aName, aDisallowInherit) { |
|
2723 return Services.droppedLinkHandler.dropLink(aEvent, aName, aDisallowInherit); |
|
2724 } |
|
2725 }; |
|
2726 |
|
2727 var homeButtonObserver = { |
|
2728 onDrop: function (aEvent) |
|
2729 { |
|
2730 // disallow setting home pages that inherit the principal |
|
2731 let url = browserDragAndDrop.drop(aEvent, {}, true); |
|
2732 setTimeout(openHomeDialog, 0, url); |
|
2733 }, |
|
2734 |
|
2735 onDragOver: function (aEvent) |
|
2736 { |
|
2737 browserDragAndDrop.dragOver(aEvent); |
|
2738 aEvent.dropEffect = "link"; |
|
2739 }, |
|
2740 onDragExit: function (aEvent) |
|
2741 { |
|
2742 } |
|
2743 } |
|
2744 |
|
2745 function openHomeDialog(aURL) |
|
2746 { |
|
2747 var promptTitle = gNavigatorBundle.getString("droponhometitle"); |
|
2748 var promptMsg = gNavigatorBundle.getString("droponhomemsg"); |
|
2749 var pressedVal = Services.prompt.confirmEx(window, promptTitle, promptMsg, |
|
2750 Services.prompt.STD_YES_NO_BUTTONS, |
|
2751 null, null, null, null, {value:0}); |
|
2752 |
|
2753 if (pressedVal == 0) { |
|
2754 try { |
|
2755 var str = Components.classes["@mozilla.org/supports-string;1"] |
|
2756 .createInstance(Components.interfaces.nsISupportsString); |
|
2757 str.data = aURL; |
|
2758 gPrefService.setComplexValue("browser.startup.homepage", |
|
2759 Components.interfaces.nsISupportsString, str); |
|
2760 } catch (ex) { |
|
2761 dump("Failed to set the home page.\n"+ex+"\n"); |
|
2762 } |
|
2763 } |
|
2764 } |
|
2765 |
|
2766 var newTabButtonObserver = { |
|
2767 onDragOver: function (aEvent) |
|
2768 { |
|
2769 browserDragAndDrop.dragOver(aEvent); |
|
2770 }, |
|
2771 |
|
2772 onDragExit: function (aEvent) |
|
2773 { |
|
2774 }, |
|
2775 |
|
2776 onDrop: function (aEvent) |
|
2777 { |
|
2778 let url = browserDragAndDrop.drop(aEvent, { }); |
|
2779 getShortcutOrURIAndPostData(url, data => { |
|
2780 if (data.url) { |
|
2781 // allow third-party services to fixup this URL |
|
2782 openNewTabWith(data.url, null, data.postData, aEvent, true); |
|
2783 } |
|
2784 }); |
|
2785 } |
|
2786 } |
|
2787 |
|
2788 var newWindowButtonObserver = { |
|
2789 onDragOver: function (aEvent) |
|
2790 { |
|
2791 browserDragAndDrop.dragOver(aEvent); |
|
2792 }, |
|
2793 onDragExit: function (aEvent) |
|
2794 { |
|
2795 }, |
|
2796 onDrop: function (aEvent) |
|
2797 { |
|
2798 let url = browserDragAndDrop.drop(aEvent, { }); |
|
2799 getShortcutOrURIAndPostData(url, data => { |
|
2800 if (data.url) { |
|
2801 // allow third-party services to fixup this URL |
|
2802 openNewWindowWith(data.url, null, data.postData, true); |
|
2803 } |
|
2804 }); |
|
2805 } |
|
2806 } |
|
2807 |
|
2808 const DOMLinkHandler = { |
|
2809 init: function() { |
|
2810 let mm = window.messageManager; |
|
2811 mm.addMessageListener("Link:AddFeed", this); |
|
2812 mm.addMessageListener("Link:AddIcon", this); |
|
2813 mm.addMessageListener("Link:AddSearch", this); |
|
2814 }, |
|
2815 |
|
2816 receiveMessage: function (aMsg) { |
|
2817 switch (aMsg.name) { |
|
2818 case "Link:AddFeed": |
|
2819 let link = {type: aMsg.data.type, href: aMsg.data.href, title: aMsg.data.title}; |
|
2820 FeedHandler.addFeed(link, aMsg.target); |
|
2821 break; |
|
2822 |
|
2823 case "Link:AddIcon": |
|
2824 return this.addIcon(aMsg.target, aMsg.data.url); |
|
2825 break; |
|
2826 |
|
2827 case "Link:AddSearch": |
|
2828 this.addSearch(aMsg.target, aMsg.data.engine, aMsg.data.url); |
|
2829 break; |
|
2830 } |
|
2831 }, |
|
2832 |
|
2833 addIcon: function(aBrowser, aURL) { |
|
2834 if (gBrowser.isFailedIcon(aURL)) |
|
2835 return false; |
|
2836 |
|
2837 let tab = gBrowser._getTabForBrowser(aBrowser); |
|
2838 if (!tab) |
|
2839 return false; |
|
2840 |
|
2841 gBrowser.setIcon(tab, aURL); |
|
2842 return true; |
|
2843 }, |
|
2844 |
|
2845 addSearch: function(aBrowser, aEngine, aURL) { |
|
2846 let tab = gBrowser._getTabForBrowser(aBrowser); |
|
2847 if (!tab) |
|
2848 return false; |
|
2849 |
|
2850 BrowserSearch.addEngine(aBrowser, aEngine, makeURI(aURL)); |
|
2851 }, |
|
2852 } |
|
2853 |
|
2854 const BrowserSearch = { |
|
2855 addEngine: function(browser, engine, uri) { |
|
2856 if (!this.searchBar) |
|
2857 return; |
|
2858 |
|
2859 // Check to see whether we've already added an engine with this title |
|
2860 if (browser.engines) { |
|
2861 if (browser.engines.some(function (e) e.title == engine.title)) |
|
2862 return; |
|
2863 } |
|
2864 |
|
2865 // Append the URI and an appropriate title to the browser data. |
|
2866 // Use documentURIObject in the check for shouldLoadFavIcon so that we |
|
2867 // do the right thing with about:-style error pages. Bug 453442 |
|
2868 var iconURL = null; |
|
2869 if (gBrowser.shouldLoadFavIcon(uri)) |
|
2870 iconURL = uri.prePath + "/favicon.ico"; |
|
2871 |
|
2872 var hidden = false; |
|
2873 // If this engine (identified by title) is already in the list, add it |
|
2874 // to the list of hidden engines rather than to the main list. |
|
2875 // XXX This will need to be changed when engines are identified by URL; |
|
2876 // see bug 335102. |
|
2877 if (Services.search.getEngineByName(engine.title)) |
|
2878 hidden = true; |
|
2879 |
|
2880 var engines = (hidden ? browser.hiddenEngines : browser.engines) || []; |
|
2881 |
|
2882 engines.push({ uri: engine.href, |
|
2883 title: engine.title, |
|
2884 icon: iconURL }); |
|
2885 |
|
2886 if (hidden) |
|
2887 browser.hiddenEngines = engines; |
|
2888 else |
|
2889 browser.engines = engines; |
|
2890 }, |
|
2891 |
|
2892 /** |
|
2893 * Gives focus to the search bar, if it is present on the toolbar, or loads |
|
2894 * the default engine's search form otherwise. For Mac, opens a new window |
|
2895 * or focuses an existing window, if necessary. |
|
2896 */ |
|
2897 webSearch: function BrowserSearch_webSearch() { |
|
2898 #ifdef XP_MACOSX |
|
2899 if (window.location.href != getBrowserURL()) { |
|
2900 var win = getTopWin(); |
|
2901 if (win) { |
|
2902 // If there's an open browser window, it should handle this command |
|
2903 win.focus(); |
|
2904 win.BrowserSearch.webSearch(); |
|
2905 } else { |
|
2906 // If there are no open browser windows, open a new one |
|
2907 var observer = function observer(subject, topic, data) { |
|
2908 if (subject == win) { |
|
2909 BrowserSearch.webSearch(); |
|
2910 Services.obs.removeObserver(observer, "browser-delayed-startup-finished"); |
|
2911 } |
|
2912 } |
|
2913 win = window.openDialog(getBrowserURL(), "_blank", |
|
2914 "chrome,all,dialog=no", "about:blank"); |
|
2915 Services.obs.addObserver(observer, "browser-delayed-startup-finished", false); |
|
2916 } |
|
2917 return; |
|
2918 } |
|
2919 #endif |
|
2920 let openSearchPageIfFieldIsNotActive = function(aSearchBar) { |
|
2921 if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) |
|
2922 openUILinkIn("about:home", "current"); |
|
2923 }; |
|
2924 |
|
2925 let searchBar = this.searchBar; |
|
2926 let placement = CustomizableUI.getPlacementOfWidget("search-container"); |
|
2927 let focusSearchBar = () => { |
|
2928 searchBar = this.searchBar; |
|
2929 searchBar.select(); |
|
2930 openSearchPageIfFieldIsNotActive(searchBar); |
|
2931 }; |
|
2932 if (placement && placement.area == CustomizableUI.AREA_PANEL) { |
|
2933 // The panel is not constructed until the first time it is shown. |
|
2934 PanelUI.show().then(focusSearchBar); |
|
2935 return; |
|
2936 } |
|
2937 if (placement && placement.area == CustomizableUI.AREA_NAVBAR && searchBar && |
|
2938 searchBar.parentNode.getAttribute("overflowedItem") == "true") { |
|
2939 let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR); |
|
2940 navBar.overflowable.show().then(() => { |
|
2941 focusSearchBar(); |
|
2942 }); |
|
2943 return; |
|
2944 } |
|
2945 if (searchBar) { |
|
2946 if (window.fullScreen) |
|
2947 FullScreen.mouseoverToggle(true); |
|
2948 searchBar.select(); |
|
2949 } |
|
2950 openSearchPageIfFieldIsNotActive(searchBar); |
|
2951 }, |
|
2952 |
|
2953 /** |
|
2954 * Loads a search results page, given a set of search terms. Uses the current |
|
2955 * engine if the search bar is visible, or the default engine otherwise. |
|
2956 * |
|
2957 * @param searchText |
|
2958 * The search terms to use for the search. |
|
2959 * |
|
2960 * @param useNewTab |
|
2961 * Boolean indicating whether or not the search should load in a new |
|
2962 * tab. |
|
2963 * |
|
2964 * @param purpose [optional] |
|
2965 * A string meant to indicate the context of the search request. This |
|
2966 * allows the search service to provide a different nsISearchSubmission |
|
2967 * depending on e.g. where the search is triggered in the UI. |
|
2968 * |
|
2969 * @return engine The search engine used to perform a search, or null if no |
|
2970 * search was performed. |
|
2971 */ |
|
2972 _loadSearch: function (searchText, useNewTab, purpose) { |
|
2973 let engine; |
|
2974 |
|
2975 // If the search bar is visible, use the current engine, otherwise, fall |
|
2976 // back to the default engine. |
|
2977 if (isElementVisible(this.searchBar)) |
|
2978 engine = Services.search.currentEngine; |
|
2979 else |
|
2980 engine = Services.search.defaultEngine; |
|
2981 |
|
2982 let submission = engine.getSubmission(searchText, null, purpose); // HTML response |
|
2983 |
|
2984 // getSubmission can return null if the engine doesn't have a URL |
|
2985 // with a text/html response type. This is unlikely (since |
|
2986 // SearchService._addEngineToStore() should fail for such an engine), |
|
2987 // but let's be on the safe side. |
|
2988 if (!submission) { |
|
2989 return null; |
|
2990 } |
|
2991 |
|
2992 let inBackground = Services.prefs.getBoolPref("browser.search.context.loadInBackground"); |
|
2993 openLinkIn(submission.uri.spec, |
|
2994 useNewTab ? "tab" : "current", |
|
2995 { postData: submission.postData, |
|
2996 inBackground: inBackground, |
|
2997 relatedToCurrent: true }); |
|
2998 |
|
2999 return engine; |
|
3000 }, |
|
3001 |
|
3002 /** |
|
3003 * Just like _loadSearch, but preserving an old API. |
|
3004 * |
|
3005 * @return string Name of the search engine used to perform a search or null |
|
3006 * if a search was not performed. |
|
3007 */ |
|
3008 loadSearch: function BrowserSearch_search(searchText, useNewTab, purpose) { |
|
3009 let engine = BrowserSearch._loadSearch(searchText, useNewTab, purpose); |
|
3010 if (!engine) { |
|
3011 return null; |
|
3012 } |
|
3013 return engine.name; |
|
3014 }, |
|
3015 |
|
3016 /** |
|
3017 * Perform a search initiated from the context menu. |
|
3018 * |
|
3019 * This should only be called from the context menu. See |
|
3020 * BrowserSearch.loadSearch for the preferred API. |
|
3021 */ |
|
3022 loadSearchFromContext: function (terms) { |
|
3023 let engine = BrowserSearch._loadSearch(terms, true, "contextmenu"); |
|
3024 if (engine) { |
|
3025 BrowserSearch.recordSearchInHealthReport(engine, "contextmenu"); |
|
3026 } |
|
3027 }, |
|
3028 |
|
3029 /** |
|
3030 * Returns the search bar element if it is present in the toolbar, null otherwise. |
|
3031 */ |
|
3032 get searchBar() { |
|
3033 return document.getElementById("searchbar"); |
|
3034 }, |
|
3035 |
|
3036 loadAddEngines: function BrowserSearch_loadAddEngines() { |
|
3037 var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow"); |
|
3038 var where = newWindowPref == 3 ? "tab" : "window"; |
|
3039 var searchEnginesURL = formatURL("browser.search.searchEnginesURL", true); |
|
3040 openUILinkIn(searchEnginesURL, where); |
|
3041 }, |
|
3042 |
|
3043 /** |
|
3044 * Helper to record a search with Firefox Health Report. |
|
3045 * |
|
3046 * FHR records only search counts and nothing pertaining to the search itself. |
|
3047 * |
|
3048 * @param engine |
|
3049 * (nsISearchEngine) The engine handling the search. |
|
3050 * @param source |
|
3051 * (string) Where the search originated from. See the FHR |
|
3052 * SearchesProvider for allowed values. |
|
3053 */ |
|
3054 recordSearchInHealthReport: function (engine, source) { |
|
3055 #ifdef MOZ_SERVICES_HEALTHREPORT |
|
3056 let reporter = Cc["@mozilla.org/datareporting/service;1"] |
|
3057 .getService() |
|
3058 .wrappedJSObject |
|
3059 .healthReporter; |
|
3060 |
|
3061 // This can happen if the FHR component of the data reporting service is |
|
3062 // disabled. This is controlled by a pref that most will never use. |
|
3063 if (!reporter) { |
|
3064 return; |
|
3065 } |
|
3066 |
|
3067 reporter.onInit().then(function record() { |
|
3068 try { |
|
3069 reporter.getProvider("org.mozilla.searches").recordSearch(engine, source); |
|
3070 } catch (ex) { |
|
3071 Cu.reportError(ex); |
|
3072 } |
|
3073 }); |
|
3074 #endif |
|
3075 }, |
|
3076 }; |
|
3077 |
|
3078 function FillHistoryMenu(aParent) { |
|
3079 // Lazily add the hover listeners on first showing and never remove them |
|
3080 if (!aParent.hasStatusListener) { |
|
3081 // Show history item's uri in the status bar when hovering, and clear on exit |
|
3082 aParent.addEventListener("DOMMenuItemActive", function(aEvent) { |
|
3083 // Only the current page should have the checked attribute, so skip it |
|
3084 if (!aEvent.target.hasAttribute("checked")) |
|
3085 XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri")); |
|
3086 }, false); |
|
3087 aParent.addEventListener("DOMMenuItemInactive", function() { |
|
3088 XULBrowserWindow.setOverLink(""); |
|
3089 }, false); |
|
3090 |
|
3091 aParent.hasStatusListener = true; |
|
3092 } |
|
3093 |
|
3094 // Remove old entries if any |
|
3095 var children = aParent.childNodes; |
|
3096 for (var i = children.length - 1; i >= 0; --i) { |
|
3097 if (children[i].hasAttribute("index")) |
|
3098 aParent.removeChild(children[i]); |
|
3099 } |
|
3100 |
|
3101 var webNav = gBrowser.webNavigation; |
|
3102 var sessionHistory = webNav.sessionHistory; |
|
3103 |
|
3104 var count = sessionHistory.count; |
|
3105 if (count <= 1) // don't display the popup for a single item |
|
3106 return false; |
|
3107 |
|
3108 const MAX_HISTORY_MENU_ITEMS = 15; |
|
3109 var index = sessionHistory.index; |
|
3110 var half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2); |
|
3111 var start = Math.max(index - half_length, 0); |
|
3112 var end = Math.min(start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1, count); |
|
3113 if (end == count) |
|
3114 start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0); |
|
3115 |
|
3116 var tooltipBack = gNavigatorBundle.getString("tabHistory.goBack"); |
|
3117 var tooltipCurrent = gNavigatorBundle.getString("tabHistory.current"); |
|
3118 var tooltipForward = gNavigatorBundle.getString("tabHistory.goForward"); |
|
3119 |
|
3120 for (var j = end - 1; j >= start; j--) { |
|
3121 let item = document.createElement("menuitem"); |
|
3122 let entry = sessionHistory.getEntryAtIndex(j, false); |
|
3123 let uri = entry.URI.spec; |
|
3124 |
|
3125 item.setAttribute("uri", uri); |
|
3126 item.setAttribute("label", entry.title || uri); |
|
3127 item.setAttribute("index", j); |
|
3128 |
|
3129 if (j != index) { |
|
3130 PlacesUtils.favicons.getFaviconURLForPage(entry.URI, function (aURI) { |
|
3131 if (aURI) { |
|
3132 let iconURL = PlacesUtils.favicons.getFaviconLinkForIcon(aURI).spec; |
|
3133 item.style.listStyleImage = "url(" + iconURL + ")"; |
|
3134 } |
|
3135 }); |
|
3136 } |
|
3137 |
|
3138 if (j < index) { |
|
3139 item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon"; |
|
3140 item.setAttribute("tooltiptext", tooltipBack); |
|
3141 } else if (j == index) { |
|
3142 item.setAttribute("type", "radio"); |
|
3143 item.setAttribute("checked", "true"); |
|
3144 item.className = "unified-nav-current"; |
|
3145 item.setAttribute("tooltiptext", tooltipCurrent); |
|
3146 } else { |
|
3147 item.className = "unified-nav-forward menuitem-iconic menuitem-with-favicon"; |
|
3148 item.setAttribute("tooltiptext", tooltipForward); |
|
3149 } |
|
3150 |
|
3151 aParent.appendChild(item); |
|
3152 } |
|
3153 return true; |
|
3154 } |
|
3155 |
|
3156 function addToUrlbarHistory(aUrlToAdd) { |
|
3157 if (!PrivateBrowsingUtils.isWindowPrivate(window) && |
|
3158 aUrlToAdd && |
|
3159 !aUrlToAdd.contains(" ") && |
|
3160 !/[\x00-\x1F]/.test(aUrlToAdd)) |
|
3161 PlacesUIUtils.markPageAsTyped(aUrlToAdd); |
|
3162 } |
|
3163 |
|
3164 function toJavaScriptConsole() |
|
3165 { |
|
3166 toOpenWindowByType("global:console", "chrome://global/content/console.xul"); |
|
3167 } |
|
3168 |
|
3169 function BrowserDownloadsUI() |
|
3170 { |
|
3171 Cc["@mozilla.org/download-manager-ui;1"]. |
|
3172 getService(Ci.nsIDownloadManagerUI).show(window); |
|
3173 } |
|
3174 |
|
3175 function toOpenWindowByType(inType, uri, features) |
|
3176 { |
|
3177 var topWindow = Services.wm.getMostRecentWindow(inType); |
|
3178 |
|
3179 if (topWindow) |
|
3180 topWindow.focus(); |
|
3181 else if (features) |
|
3182 window.open(uri, "_blank", features); |
|
3183 else |
|
3184 window.open(uri, "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar"); |
|
3185 } |
|
3186 |
|
3187 function OpenBrowserWindow(options) |
|
3188 { |
|
3189 var telemetryObj = {}; |
|
3190 TelemetryStopwatch.start("FX_NEW_WINDOW_MS", telemetryObj); |
|
3191 |
|
3192 function newDocumentShown(doc, topic, data) { |
|
3193 if (topic == "document-shown" && |
|
3194 doc != document && |
|
3195 doc.defaultView == win) { |
|
3196 Services.obs.removeObserver(newDocumentShown, "document-shown"); |
|
3197 Services.obs.removeObserver(windowClosed, "domwindowclosed"); |
|
3198 TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj); |
|
3199 } |
|
3200 } |
|
3201 |
|
3202 function windowClosed(subject) { |
|
3203 if (subject == win) { |
|
3204 Services.obs.removeObserver(newDocumentShown, "document-shown"); |
|
3205 Services.obs.removeObserver(windowClosed, "domwindowclosed"); |
|
3206 } |
|
3207 } |
|
3208 |
|
3209 // Make sure to remove the 'document-shown' observer in case the window |
|
3210 // is being closed right after it was opened to avoid leaking. |
|
3211 Services.obs.addObserver(newDocumentShown, "document-shown", false); |
|
3212 Services.obs.addObserver(windowClosed, "domwindowclosed", false); |
|
3213 |
|
3214 var charsetArg = new String(); |
|
3215 var handler = Components.classes["@mozilla.org/browser/clh;1"] |
|
3216 .getService(Components.interfaces.nsIBrowserHandler); |
|
3217 var defaultArgs = handler.defaultArgs; |
|
3218 var wintype = document.documentElement.getAttribute('windowtype'); |
|
3219 |
|
3220 var extraFeatures = ""; |
|
3221 if (options && options.private) { |
|
3222 extraFeatures = ",private"; |
|
3223 if (!PrivateBrowsingUtils.permanentPrivateBrowsing) { |
|
3224 // Force the new window to load about:privatebrowsing instead of the default home page |
|
3225 defaultArgs = "about:privatebrowsing"; |
|
3226 } |
|
3227 } else { |
|
3228 extraFeatures = ",non-private"; |
|
3229 } |
|
3230 |
|
3231 if (options && options.remote) { |
|
3232 let omtcEnabled = gPrefService.getBoolPref("layers.offmainthreadcomposition.enabled"); |
|
3233 if (!omtcEnabled) { |
|
3234 alert("To use out-of-process tabs, you must set the layers.offmainthreadcomposition.enabled preference and restart. Opening a normal window instead."); |
|
3235 } else { |
|
3236 extraFeatures += ",remote"; |
|
3237 } |
|
3238 } else if (options && options.remote === false) { |
|
3239 extraFeatures += ",non-remote"; |
|
3240 } |
|
3241 |
|
3242 // if and only if the current window is a browser window and it has a document with a character |
|
3243 // set, then extract the current charset menu setting from the current document and use it to |
|
3244 // initialize the new browser window... |
|
3245 var win; |
|
3246 if (window && (wintype == "navigator:browser") && window.content && window.content.document) |
|
3247 { |
|
3248 var DocCharset = window.content.document.characterSet; |
|
3249 charsetArg = "charset="+DocCharset; |
|
3250 |
|
3251 //we should "inherit" the charset menu setting in a new window |
|
3252 win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs, charsetArg); |
|
3253 } |
|
3254 else // forget about the charset information. |
|
3255 { |
|
3256 win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs); |
|
3257 } |
|
3258 |
|
3259 return win; |
|
3260 } |
|
3261 |
|
3262 // Only here for backwards compat, we should remove this soon |
|
3263 function BrowserCustomizeToolbar() { |
|
3264 gCustomizeMode.enter(); |
|
3265 } |
|
3266 |
|
3267 /** |
|
3268 * Update the global flag that tracks whether or not any edit UI (the Edit menu, |
|
3269 * edit-related items in the context menu, and edit-related toolbar buttons |
|
3270 * is visible, then update the edit commands' enabled state accordingly. We use |
|
3271 * this flag to skip updating the edit commands on focus or selection changes |
|
3272 * when no UI is visible to improve performance (including pageload performance, |
|
3273 * since focus changes when you load a new page). |
|
3274 * |
|
3275 * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands' |
|
3276 * enabled state so the UI will reflect it appropriately. |
|
3277 * |
|
3278 * If the UI isn't visible, we enable all edit commands so keyboard shortcuts |
|
3279 * still work and just lazily disable them as needed when the user presses a |
|
3280 * shortcut. |
|
3281 * |
|
3282 * This doesn't work on Mac, since Mac menus flash when users press their |
|
3283 * keyboard shortcuts, so edit UI is essentially always visible on the Mac, |
|
3284 * and we need to always update the edit commands. Thus on Mac this function |
|
3285 * is a no op. |
|
3286 */ |
|
3287 function updateEditUIVisibility() |
|
3288 { |
|
3289 #ifndef XP_MACOSX |
|
3290 let editMenuPopupState = document.getElementById("menu_EditPopup").state; |
|
3291 let contextMenuPopupState = document.getElementById("contentAreaContextMenu").state; |
|
3292 let placesContextMenuPopupState = document.getElementById("placesContext").state; |
|
3293 |
|
3294 // The UI is visible if the Edit menu is opening or open, if the context menu |
|
3295 // is open, or if the toolbar has been customized to include the Cut, Copy, |
|
3296 // or Paste toolbar buttons. |
|
3297 gEditUIVisible = editMenuPopupState == "showing" || |
|
3298 editMenuPopupState == "open" || |
|
3299 contextMenuPopupState == "showing" || |
|
3300 contextMenuPopupState == "open" || |
|
3301 placesContextMenuPopupState == "showing" || |
|
3302 placesContextMenuPopupState == "open" || |
|
3303 document.getElementById("edit-controls") ? true : false; |
|
3304 |
|
3305 // If UI is visible, update the edit commands' enabled state to reflect |
|
3306 // whether or not they are actually enabled for the current focus/selection. |
|
3307 if (gEditUIVisible) |
|
3308 goUpdateGlobalEditMenuItems(); |
|
3309 |
|
3310 // Otherwise, enable all commands, so that keyboard shortcuts still work, |
|
3311 // then lazily determine their actual enabled state when the user presses |
|
3312 // a keyboard shortcut. |
|
3313 else { |
|
3314 goSetCommandEnabled("cmd_undo", true); |
|
3315 goSetCommandEnabled("cmd_redo", true); |
|
3316 goSetCommandEnabled("cmd_cut", true); |
|
3317 goSetCommandEnabled("cmd_copy", true); |
|
3318 goSetCommandEnabled("cmd_paste", true); |
|
3319 goSetCommandEnabled("cmd_selectAll", true); |
|
3320 goSetCommandEnabled("cmd_delete", true); |
|
3321 goSetCommandEnabled("cmd_switchTextDirection", true); |
|
3322 } |
|
3323 #endif |
|
3324 } |
|
3325 |
|
3326 /** |
|
3327 * Makes the Character Encoding menu enabled or disabled as appropriate. |
|
3328 * To be called when the View menu or the app menu is opened. |
|
3329 */ |
|
3330 function updateCharacterEncodingMenuState() |
|
3331 { |
|
3332 let charsetMenu = document.getElementById("charsetMenu"); |
|
3333 // gBrowser is null on Mac when the menubar shows in the context of |
|
3334 // non-browser windows. The above elements may be null depending on |
|
3335 // what parts of the menubar are present. E.g. no app menu on Mac. |
|
3336 if (gBrowser && |
|
3337 gBrowser.docShell && |
|
3338 gBrowser.docShell.mayEnableCharacterEncodingMenu) { |
|
3339 if (charsetMenu) { |
|
3340 charsetMenu.removeAttribute("disabled"); |
|
3341 } |
|
3342 } else { |
|
3343 if (charsetMenu) { |
|
3344 charsetMenu.setAttribute("disabled", "true"); |
|
3345 } |
|
3346 } |
|
3347 } |
|
3348 |
|
3349 /** |
|
3350 * Returns true if |aMimeType| is text-based, false otherwise. |
|
3351 * |
|
3352 * @param aMimeType |
|
3353 * The MIME type to check. |
|
3354 * |
|
3355 * If adding types to this function, please also check the similar |
|
3356 * function in findbar.xml |
|
3357 */ |
|
3358 function mimeTypeIsTextBased(aMimeType) |
|
3359 { |
|
3360 return aMimeType.startsWith("text/") || |
|
3361 aMimeType.endsWith("+xml") || |
|
3362 aMimeType == "application/x-javascript" || |
|
3363 aMimeType == "application/javascript" || |
|
3364 aMimeType == "application/json" || |
|
3365 aMimeType == "application/xml" || |
|
3366 aMimeType == "mozilla.application/cached-xul"; |
|
3367 } |
|
3368 |
|
3369 var XULBrowserWindow = { |
|
3370 // Stored Status, Link and Loading values |
|
3371 status: "", |
|
3372 defaultStatus: "", |
|
3373 overLink: "", |
|
3374 startTime: 0, |
|
3375 statusText: "", |
|
3376 isBusy: false, |
|
3377 // Left here for add-on compatibility, see bug 752434 |
|
3378 inContentWhitelist: [], |
|
3379 |
|
3380 QueryInterface: function (aIID) { |
|
3381 if (aIID.equals(Ci.nsIWebProgressListener) || |
|
3382 aIID.equals(Ci.nsIWebProgressListener2) || |
|
3383 aIID.equals(Ci.nsISupportsWeakReference) || |
|
3384 aIID.equals(Ci.nsIXULBrowserWindow) || |
|
3385 aIID.equals(Ci.nsISupports)) |
|
3386 return this; |
|
3387 throw Cr.NS_NOINTERFACE; |
|
3388 }, |
|
3389 |
|
3390 get stopCommand () { |
|
3391 delete this.stopCommand; |
|
3392 return this.stopCommand = document.getElementById("Browser:Stop"); |
|
3393 }, |
|
3394 get reloadCommand () { |
|
3395 delete this.reloadCommand; |
|
3396 return this.reloadCommand = document.getElementById("Browser:Reload"); |
|
3397 }, |
|
3398 get statusTextField () { |
|
3399 return gBrowser.getStatusPanel(); |
|
3400 }, |
|
3401 get isImage () { |
|
3402 delete this.isImage; |
|
3403 return this.isImage = document.getElementById("isImage"); |
|
3404 }, |
|
3405 |
|
3406 init: function () { |
|
3407 // Initialize the security button's state and tooltip text. |
|
3408 var securityUI = gBrowser.securityUI; |
|
3409 this.onSecurityChange(null, null, securityUI.state); |
|
3410 }, |
|
3411 |
|
3412 setJSStatus: function () { |
|
3413 // unsupported |
|
3414 }, |
|
3415 |
|
3416 setDefaultStatus: function (status) { |
|
3417 this.defaultStatus = status; |
|
3418 this.updateStatusField(); |
|
3419 }, |
|
3420 |
|
3421 setOverLink: function (url, anchorElt) { |
|
3422 // Encode bidirectional formatting characters. |
|
3423 // (RFC 3987 sections 3.2 and 4.1 paragraph 6) |
|
3424 url = url.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g, |
|
3425 encodeURIComponent); |
|
3426 |
|
3427 if (gURLBar && gURLBar._mayTrimURLs /* corresponds to browser.urlbar.trimURLs */) |
|
3428 url = trimURL(url); |
|
3429 |
|
3430 this.overLink = url; |
|
3431 LinkTargetDisplay.update(); |
|
3432 }, |
|
3433 |
|
3434 showTooltip: function (x, y, tooltip) { |
|
3435 // The x,y coordinates are relative to the <browser> element using |
|
3436 // the chrome zoom level. |
|
3437 let elt = document.getElementById("remoteBrowserTooltip"); |
|
3438 elt.label = tooltip; |
|
3439 |
|
3440 let anchor = gBrowser.selectedBrowser; |
|
3441 elt.openPopupAtScreen(anchor.boxObject.screenX + x, anchor.boxObject.screenY + y, false, null); |
|
3442 }, |
|
3443 |
|
3444 hideTooltip: function () { |
|
3445 let elt = document.getElementById("remoteBrowserTooltip"); |
|
3446 elt.hidePopup(); |
|
3447 }, |
|
3448 |
|
3449 updateStatusField: function () { |
|
3450 var text, type, types = ["overLink"]; |
|
3451 if (this._busyUI) |
|
3452 types.push("status"); |
|
3453 types.push("defaultStatus"); |
|
3454 for (type of types) { |
|
3455 text = this[type]; |
|
3456 if (text) |
|
3457 break; |
|
3458 } |
|
3459 |
|
3460 // check the current value so we don't trigger an attribute change |
|
3461 // and cause needless (slow!) UI updates |
|
3462 if (this.statusText != text) { |
|
3463 let field = this.statusTextField; |
|
3464 field.setAttribute("previoustype", field.getAttribute("type")); |
|
3465 field.setAttribute("type", type); |
|
3466 field.label = text; |
|
3467 field.setAttribute("crop", type == "overLink" ? "center" : "end"); |
|
3468 this.statusText = text; |
|
3469 } |
|
3470 }, |
|
3471 |
|
3472 // Called before links are navigated to to allow us to retarget them if needed. |
|
3473 onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) { |
|
3474 let target = this._onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab); |
|
3475 SocialUI.closeSocialPanelForLinkTraversal(target, linkNode); |
|
3476 return target; |
|
3477 }, |
|
3478 |
|
3479 _onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) { |
|
3480 // Don't modify non-default targets or targets that aren't in top-level app |
|
3481 // tab docshells (isAppTab will be false for app tab subframes). |
|
3482 if (originalTarget != "" || !isAppTab) |
|
3483 return originalTarget; |
|
3484 |
|
3485 // External links from within app tabs should always open in new tabs |
|
3486 // instead of replacing the app tab's page (Bug 575561) |
|
3487 let linkHost; |
|
3488 let docHost; |
|
3489 try { |
|
3490 linkHost = linkURI.host; |
|
3491 docHost = linkNode.ownerDocument.documentURIObject.host; |
|
3492 } catch(e) { |
|
3493 // nsIURI.host can throw for non-nsStandardURL nsIURIs. |
|
3494 // If we fail to get either host, just return originalTarget. |
|
3495 return originalTarget; |
|
3496 } |
|
3497 |
|
3498 if (docHost == linkHost) |
|
3499 return originalTarget; |
|
3500 |
|
3501 // Special case: ignore "www" prefix if it is part of host string |
|
3502 let [longHost, shortHost] = |
|
3503 linkHost.length > docHost.length ? [linkHost, docHost] : [docHost, linkHost]; |
|
3504 if (longHost == "www." + shortHost) |
|
3505 return originalTarget; |
|
3506 |
|
3507 return "_blank"; |
|
3508 }, |
|
3509 |
|
3510 onProgressChange: function (aWebProgress, aRequest, |
|
3511 aCurSelfProgress, aMaxSelfProgress, |
|
3512 aCurTotalProgress, aMaxTotalProgress) { |
|
3513 // Do nothing. |
|
3514 }, |
|
3515 |
|
3516 onProgressChange64: function (aWebProgress, aRequest, |
|
3517 aCurSelfProgress, aMaxSelfProgress, |
|
3518 aCurTotalProgress, aMaxTotalProgress) { |
|
3519 return this.onProgressChange(aWebProgress, aRequest, |
|
3520 aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, |
|
3521 aMaxTotalProgress); |
|
3522 }, |
|
3523 |
|
3524 // This function fires only for the currently selected tab. |
|
3525 onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) { |
|
3526 const nsIWebProgressListener = Ci.nsIWebProgressListener; |
|
3527 const nsIChannel = Ci.nsIChannel; |
|
3528 |
|
3529 let browser = gBrowser.selectedBrowser; |
|
3530 |
|
3531 if (aStateFlags & nsIWebProgressListener.STATE_START && |
|
3532 aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { |
|
3533 |
|
3534 if (aRequest && aWebProgress.isTopLevel) { |
|
3535 // clear out feed data |
|
3536 browser.feeds = null; |
|
3537 |
|
3538 // clear out search-engine data |
|
3539 browser.engines = null; |
|
3540 } |
|
3541 |
|
3542 this.isBusy = true; |
|
3543 |
|
3544 if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) { |
|
3545 this._busyUI = true; |
|
3546 |
|
3547 // XXX: This needs to be based on window activity... |
|
3548 this.stopCommand.removeAttribute("disabled"); |
|
3549 CombinedStopReload.switchToStop(); |
|
3550 } |
|
3551 } |
|
3552 else if (aStateFlags & nsIWebProgressListener.STATE_STOP) { |
|
3553 // This (thanks to the filter) is a network stop or the last |
|
3554 // request stop outside of loading the document, stop throbbers |
|
3555 // and progress bars and such |
|
3556 if (aRequest) { |
|
3557 let msg = ""; |
|
3558 let location; |
|
3559 // Get the URI either from a channel or a pseudo-object |
|
3560 if (aRequest instanceof nsIChannel || "URI" in aRequest) { |
|
3561 location = aRequest.URI; |
|
3562 |
|
3563 // For keyword URIs clear the user typed value since they will be changed into real URIs |
|
3564 if (location.scheme == "keyword" && aWebProgress.isTopLevel) |
|
3565 gBrowser.userTypedValue = null; |
|
3566 |
|
3567 if (location.spec != "about:blank") { |
|
3568 switch (aStatus) { |
|
3569 case Components.results.NS_ERROR_NET_TIMEOUT: |
|
3570 msg = gNavigatorBundle.getString("nv_timeout"); |
|
3571 break; |
|
3572 } |
|
3573 } |
|
3574 } |
|
3575 |
|
3576 this.status = ""; |
|
3577 this.setDefaultStatus(msg); |
|
3578 |
|
3579 // Disable menu entries for images, enable otherwise |
|
3580 if (browser.documentContentType && mimeTypeIsTextBased(browser.documentContentType)) |
|
3581 this.isImage.removeAttribute('disabled'); |
|
3582 else |
|
3583 this.isImage.setAttribute('disabled', 'true'); |
|
3584 } |
|
3585 |
|
3586 this.isBusy = false; |
|
3587 |
|
3588 if (this._busyUI) { |
|
3589 this._busyUI = false; |
|
3590 |
|
3591 this.stopCommand.setAttribute("disabled", "true"); |
|
3592 CombinedStopReload.switchToReload(aRequest instanceof Ci.nsIRequest); |
|
3593 } |
|
3594 } |
|
3595 }, |
|
3596 |
|
3597 onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) { |
|
3598 var location = aLocationURI ? aLocationURI.spec : ""; |
|
3599 |
|
3600 // Hide the form invalid popup. |
|
3601 if (gFormSubmitObserver.panel) { |
|
3602 gFormSubmitObserver.panel.hidePopup(); |
|
3603 } |
|
3604 |
|
3605 let pageTooltip = document.getElementById("aHTMLTooltip"); |
|
3606 let tooltipNode = pageTooltip.triggerNode; |
|
3607 if (tooltipNode) { |
|
3608 // Optimise for the common case |
|
3609 if (aWebProgress.isTopLevel) { |
|
3610 pageTooltip.hidePopup(); |
|
3611 } |
|
3612 else { |
|
3613 for (let tooltipWindow = tooltipNode.ownerDocument.defaultView; |
|
3614 tooltipWindow != tooltipWindow.parent; |
|
3615 tooltipWindow = tooltipWindow.parent) { |
|
3616 if (tooltipWindow == aWebProgress.DOMWindow) { |
|
3617 pageTooltip.hidePopup(); |
|
3618 break; |
|
3619 } |
|
3620 } |
|
3621 } |
|
3622 } |
|
3623 |
|
3624 let browser = gBrowser.selectedBrowser; |
|
3625 |
|
3626 // Disable menu entries for images, enable otherwise |
|
3627 if (browser.documentContentType && mimeTypeIsTextBased(browser.documentContentType)) |
|
3628 this.isImage.removeAttribute('disabled'); |
|
3629 else |
|
3630 this.isImage.setAttribute('disabled', 'true'); |
|
3631 |
|
3632 this.hideOverLinkImmediately = true; |
|
3633 this.setOverLink("", null); |
|
3634 this.hideOverLinkImmediately = false; |
|
3635 |
|
3636 // We should probably not do this if the value has changed since the user |
|
3637 // searched |
|
3638 // Update urlbar only if a new page was loaded on the primary content area |
|
3639 // Do not update urlbar if there was a subframe navigation |
|
3640 |
|
3641 if (aWebProgress.isTopLevel) { |
|
3642 if ((location == "about:blank" && (gMultiProcessBrowser || !content.opener)) || |
|
3643 location == "") { // Second condition is for new tabs, otherwise |
|
3644 // reload function is enabled until tab is refreshed. |
|
3645 this.reloadCommand.setAttribute("disabled", "true"); |
|
3646 } else { |
|
3647 this.reloadCommand.removeAttribute("disabled"); |
|
3648 } |
|
3649 |
|
3650 if (gURLBar) { |
|
3651 URLBarSetURI(aLocationURI); |
|
3652 |
|
3653 BookmarkingUI.onLocationChange(); |
|
3654 SocialUI.updateState(); |
|
3655 } |
|
3656 |
|
3657 // Utility functions for disabling find |
|
3658 var shouldDisableFind = function shouldDisableFind(aDocument) { |
|
3659 let docElt = aDocument.documentElement; |
|
3660 return docElt && docElt.getAttribute("disablefastfind") == "true"; |
|
3661 } |
|
3662 |
|
3663 var disableFindCommands = function disableFindCommands(aDisable) { |
|
3664 let findCommands = [document.getElementById("cmd_find"), |
|
3665 document.getElementById("cmd_findAgain"), |
|
3666 document.getElementById("cmd_findPrevious")]; |
|
3667 for (let elt of findCommands) { |
|
3668 if (aDisable) |
|
3669 elt.setAttribute("disabled", "true"); |
|
3670 else |
|
3671 elt.removeAttribute("disabled"); |
|
3672 } |
|
3673 } |
|
3674 |
|
3675 var onContentRSChange = function onContentRSChange(e) { |
|
3676 if (e.target.readyState != "interactive" && e.target.readyState != "complete") |
|
3677 return; |
|
3678 |
|
3679 e.target.removeEventListener("readystatechange", onContentRSChange); |
|
3680 disableFindCommands(shouldDisableFind(e.target)); |
|
3681 } |
|
3682 |
|
3683 // Disable find commands in documents that ask for them to be disabled. |
|
3684 if (!gMultiProcessBrowser && aLocationURI && |
|
3685 (aLocationURI.schemeIs("about") || aLocationURI.schemeIs("chrome"))) { |
|
3686 // Don't need to re-enable/disable find commands for same-document location changes |
|
3687 // (e.g. the replaceStates in about:addons) |
|
3688 if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) { |
|
3689 if (content.document.readyState == "interactive" || content.document.readyState == "complete") |
|
3690 disableFindCommands(shouldDisableFind(content.document)); |
|
3691 else { |
|
3692 content.document.addEventListener("readystatechange", onContentRSChange); |
|
3693 } |
|
3694 } |
|
3695 } else |
|
3696 disableFindCommands(false); |
|
3697 |
|
3698 // Try not to instantiate gCustomizeMode as much as possible, |
|
3699 // so don't use CustomizeMode.jsm to check for URI or customizing. |
|
3700 let customizingURI = "about:customizing"; |
|
3701 if (location == customizingURI) { |
|
3702 gCustomizeMode.enter(); |
|
3703 } else if (location != customizingURI && |
|
3704 (CustomizationHandler.isEnteringCustomizeMode || |
|
3705 CustomizationHandler.isCustomizing())) { |
|
3706 gCustomizeMode.exit(); |
|
3707 } |
|
3708 } |
|
3709 UpdateBackForwardCommands(gBrowser.webNavigation); |
|
3710 |
|
3711 gGestureSupport.restoreRotationState(); |
|
3712 |
|
3713 // See bug 358202, when tabs are switched during a drag operation, |
|
3714 // timers don't fire on windows (bug 203573) |
|
3715 if (aRequest) |
|
3716 setTimeout(function () { XULBrowserWindow.asyncUpdateUI(); }, 0); |
|
3717 else |
|
3718 this.asyncUpdateUI(); |
|
3719 |
|
3720 #ifdef MOZ_CRASHREPORTER |
|
3721 if (aLocationURI) { |
|
3722 let uri = aLocationURI.clone(); |
|
3723 try { |
|
3724 // If the current URI contains a username/password, remove it. |
|
3725 uri.userPass = ""; |
|
3726 } catch (ex) { /* Ignore failures on about: URIs. */ } |
|
3727 |
|
3728 try { |
|
3729 gCrashReporter.annotateCrashReport("URL", uri.spec); |
|
3730 } catch (ex if ex.result == Components.results.NS_ERROR_NOT_INITIALIZED) { |
|
3731 // Don't make noise when the crash reporter is built but not enabled. |
|
3732 } |
|
3733 } |
|
3734 #endif |
|
3735 }, |
|
3736 |
|
3737 asyncUpdateUI: function () { |
|
3738 FeedHandler.updateFeeds(); |
|
3739 }, |
|
3740 |
|
3741 // Left here for add-on compatibility, see bug 752434 |
|
3742 hideChromeForLocation: function() {}, |
|
3743 |
|
3744 onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) { |
|
3745 this.status = aMessage; |
|
3746 this.updateStatusField(); |
|
3747 }, |
|
3748 |
|
3749 // Properties used to cache security state used to update the UI |
|
3750 _state: null, |
|
3751 _lastLocation: null, |
|
3752 |
|
3753 onSecurityChange: function (aWebProgress, aRequest, aState) { |
|
3754 // Don't need to do anything if the data we use to update the UI hasn't |
|
3755 // changed |
|
3756 let uri = gBrowser.currentURI; |
|
3757 let spec = uri.spec; |
|
3758 if (this._state == aState && |
|
3759 this._lastLocation == spec) |
|
3760 return; |
|
3761 this._state = aState; |
|
3762 this._lastLocation = spec; |
|
3763 |
|
3764 // aState is defined as a bitmask that may be extended in the future. |
|
3765 // We filter out any unknown bits before testing for known values. |
|
3766 const wpl = Components.interfaces.nsIWebProgressListener; |
|
3767 const wpl_security_bits = wpl.STATE_IS_SECURE | |
|
3768 wpl.STATE_IS_BROKEN | |
|
3769 wpl.STATE_IS_INSECURE; |
|
3770 var level; |
|
3771 |
|
3772 switch (this._state & wpl_security_bits) { |
|
3773 case wpl.STATE_IS_SECURE: |
|
3774 level = "high"; |
|
3775 break; |
|
3776 case wpl.STATE_IS_BROKEN: |
|
3777 level = "broken"; |
|
3778 break; |
|
3779 } |
|
3780 |
|
3781 if (level) { |
|
3782 // We don't style the Location Bar based on the the 'level' attribute |
|
3783 // anymore, but still set it for third-party themes. |
|
3784 if (gURLBar) |
|
3785 gURLBar.setAttribute("level", level); |
|
3786 } else { |
|
3787 if (gURLBar) |
|
3788 gURLBar.removeAttribute("level"); |
|
3789 } |
|
3790 |
|
3791 try { |
|
3792 uri = Services.uriFixup.createExposableURI(uri); |
|
3793 } catch (e) {} |
|
3794 gIdentityHandler.checkIdentity(this._state, uri); |
|
3795 }, |
|
3796 |
|
3797 // simulate all change notifications after switching tabs |
|
3798 onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) { |
|
3799 if (FullZoom.updateBackgroundTabs) |
|
3800 FullZoom.onLocationChange(gBrowser.currentURI, true); |
|
3801 var nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; |
|
3802 var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP; |
|
3803 // use a pseudo-object instead of a (potentially nonexistent) channel for getting |
|
3804 // a correct error message - and make sure that the UI is always either in |
|
3805 // loading (STATE_START) or done (STATE_STOP) mode |
|
3806 this.onStateChange( |
|
3807 gBrowser.webProgress, |
|
3808 { URI: gBrowser.currentURI }, |
|
3809 loadingDone ? nsIWebProgressListener.STATE_STOP : nsIWebProgressListener.STATE_START, |
|
3810 aStatus |
|
3811 ); |
|
3812 // status message and progress value are undefined if we're done with loading |
|
3813 if (loadingDone) |
|
3814 return; |
|
3815 this.onStatusChange(gBrowser.webProgress, null, 0, aMessage); |
|
3816 } |
|
3817 }; |
|
3818 |
|
3819 var LinkTargetDisplay = { |
|
3820 get DELAY_SHOW() { |
|
3821 delete this.DELAY_SHOW; |
|
3822 return this.DELAY_SHOW = Services.prefs.getIntPref("browser.overlink-delay"); |
|
3823 }, |
|
3824 |
|
3825 DELAY_HIDE: 250, |
|
3826 _timer: 0, |
|
3827 |
|
3828 get _isVisible () XULBrowserWindow.statusTextField.label != "", |
|
3829 |
|
3830 update: function () { |
|
3831 clearTimeout(this._timer); |
|
3832 window.removeEventListener("mousemove", this, true); |
|
3833 |
|
3834 if (!XULBrowserWindow.overLink) { |
|
3835 if (XULBrowserWindow.hideOverLinkImmediately) |
|
3836 this._hide(); |
|
3837 else |
|
3838 this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE); |
|
3839 return; |
|
3840 } |
|
3841 |
|
3842 if (this._isVisible) { |
|
3843 XULBrowserWindow.updateStatusField(); |
|
3844 } else { |
|
3845 // Let the display appear when the mouse doesn't move within the delay |
|
3846 this._showDelayed(); |
|
3847 window.addEventListener("mousemove", this, true); |
|
3848 } |
|
3849 }, |
|
3850 |
|
3851 handleEvent: function (event) { |
|
3852 switch (event.type) { |
|
3853 case "mousemove": |
|
3854 // Restart the delay since the mouse was moved |
|
3855 clearTimeout(this._timer); |
|
3856 this._showDelayed(); |
|
3857 break; |
|
3858 } |
|
3859 }, |
|
3860 |
|
3861 _showDelayed: function () { |
|
3862 this._timer = setTimeout(function (self) { |
|
3863 XULBrowserWindow.updateStatusField(); |
|
3864 window.removeEventListener("mousemove", self, true); |
|
3865 }, this.DELAY_SHOW, this); |
|
3866 }, |
|
3867 |
|
3868 _hide: function () { |
|
3869 clearTimeout(this._timer); |
|
3870 |
|
3871 XULBrowserWindow.updateStatusField(); |
|
3872 } |
|
3873 }; |
|
3874 |
|
3875 var CombinedStopReload = { |
|
3876 init: function () { |
|
3877 if (this._initialized) |
|
3878 return; |
|
3879 |
|
3880 let reload = document.getElementById("urlbar-reload-button"); |
|
3881 let stop = document.getElementById("urlbar-stop-button"); |
|
3882 if (!stop || !reload || reload.nextSibling != stop) |
|
3883 return; |
|
3884 |
|
3885 this._initialized = true; |
|
3886 if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") |
|
3887 reload.setAttribute("displaystop", "true"); |
|
3888 stop.addEventListener("click", this, false); |
|
3889 this.reload = reload; |
|
3890 this.stop = stop; |
|
3891 }, |
|
3892 |
|
3893 uninit: function () { |
|
3894 if (!this._initialized) |
|
3895 return; |
|
3896 |
|
3897 this._cancelTransition(); |
|
3898 this._initialized = false; |
|
3899 this.stop.removeEventListener("click", this, false); |
|
3900 this.reload = null; |
|
3901 this.stop = null; |
|
3902 }, |
|
3903 |
|
3904 handleEvent: function (event) { |
|
3905 // the only event we listen to is "click" on the stop button |
|
3906 if (event.button == 0 && |
|
3907 !this.stop.disabled) |
|
3908 this._stopClicked = true; |
|
3909 }, |
|
3910 |
|
3911 switchToStop: function () { |
|
3912 if (!this._initialized) |
|
3913 return; |
|
3914 |
|
3915 this._cancelTransition(); |
|
3916 this.reload.setAttribute("displaystop", "true"); |
|
3917 }, |
|
3918 |
|
3919 switchToReload: function (aDelay) { |
|
3920 if (!this._initialized) |
|
3921 return; |
|
3922 |
|
3923 this.reload.removeAttribute("displaystop"); |
|
3924 |
|
3925 if (!aDelay || this._stopClicked) { |
|
3926 this._stopClicked = false; |
|
3927 this._cancelTransition(); |
|
3928 this.reload.disabled = XULBrowserWindow.reloadCommand |
|
3929 .getAttribute("disabled") == "true"; |
|
3930 return; |
|
3931 } |
|
3932 |
|
3933 if (this._timer) |
|
3934 return; |
|
3935 |
|
3936 // Temporarily disable the reload button to prevent the user from |
|
3937 // accidentally reloading the page when intending to click the stop button |
|
3938 this.reload.disabled = true; |
|
3939 this._timer = setTimeout(function (self) { |
|
3940 self._timer = 0; |
|
3941 self.reload.disabled = XULBrowserWindow.reloadCommand |
|
3942 .getAttribute("disabled") == "true"; |
|
3943 }, 650, this); |
|
3944 }, |
|
3945 |
|
3946 _cancelTransition: function () { |
|
3947 if (this._timer) { |
|
3948 clearTimeout(this._timer); |
|
3949 this._timer = 0; |
|
3950 } |
|
3951 } |
|
3952 }; |
|
3953 |
|
3954 var TabsProgressListener = { |
|
3955 onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { |
|
3956 // Collect telemetry data about tab load times. |
|
3957 if (aWebProgress.isTopLevel) { |
|
3958 if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) { |
|
3959 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) { |
|
3960 TelemetryStopwatch.start("FX_PAGE_LOAD_MS", aBrowser); |
|
3961 Services.telemetry.getHistogramById("FX_TOTAL_TOP_VISITS").add(true); |
|
3962 } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { |
|
3963 TelemetryStopwatch.finish("FX_PAGE_LOAD_MS", aBrowser); |
|
3964 } |
|
3965 } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && |
|
3966 aStatus == Cr.NS_BINDING_ABORTED) { |
|
3967 TelemetryStopwatch.cancel("FX_PAGE_LOAD_MS", aBrowser); |
|
3968 } |
|
3969 } |
|
3970 |
|
3971 // Attach a listener to watch for "click" events bubbling up from error |
|
3972 // pages and other similar pages (like about:newtab). This lets us fix bugs |
|
3973 // like 401575 which require error page UI to do privileged things, without |
|
3974 // letting error pages have any privilege themselves. |
|
3975 // We can't look for this during onLocationChange since at that point the |
|
3976 // document URI is not yet the about:-uri of the error page. |
|
3977 |
|
3978 let isRemoteBrowser = aBrowser.isRemoteBrowser; |
|
3979 // We check isRemoteBrowser here to avoid requesting the doc CPOW |
|
3980 let doc = isRemoteBrowser ? null : aWebProgress.DOMWindow.document; |
|
3981 |
|
3982 if (!isRemoteBrowser && |
|
3983 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && |
|
3984 Components.isSuccessCode(aStatus) && |
|
3985 doc.documentURI.startsWith("about:") && |
|
3986 !doc.documentURI.toLowerCase().startsWith("about:blank") && |
|
3987 !doc.documentURI.toLowerCase().startsWith("about:home") && |
|
3988 !doc.documentElement.hasAttribute("hasBrowserHandlers")) { |
|
3989 // STATE_STOP may be received twice for documents, thus store an |
|
3990 // attribute to ensure handling it just once. |
|
3991 doc.documentElement.setAttribute("hasBrowserHandlers", "true"); |
|
3992 aBrowser.addEventListener("click", BrowserOnClick, true); |
|
3993 aBrowser.addEventListener("pagehide", function onPageHide(event) { |
|
3994 if (event.target.defaultView.frameElement) |
|
3995 return; |
|
3996 aBrowser.removeEventListener("click", BrowserOnClick, true); |
|
3997 aBrowser.removeEventListener("pagehide", onPageHide, true); |
|
3998 if (event.target.documentElement) |
|
3999 event.target.documentElement.removeAttribute("hasBrowserHandlers"); |
|
4000 }, true); |
|
4001 |
|
4002 #ifdef MOZ_CRASHREPORTER |
|
4003 if (doc.documentURI.startsWith("about:tabcrashed")) |
|
4004 TabCrashReporter.onAboutTabCrashedLoad(aBrowser); |
|
4005 #endif |
|
4006 } |
|
4007 }, |
|
4008 |
|
4009 onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI, |
|
4010 aFlags) { |
|
4011 // Filter out location changes caused by anchor navigation |
|
4012 // or history.push/pop/replaceState. |
|
4013 if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) |
|
4014 return; |
|
4015 |
|
4016 // Filter out location changes in sub documents. |
|
4017 if (!aWebProgress.isTopLevel) |
|
4018 return; |
|
4019 |
|
4020 // Only need to call locationChange if the PopupNotifications object |
|
4021 // for this window has already been initialized (i.e. its getter no |
|
4022 // longer exists) |
|
4023 if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) |
|
4024 PopupNotifications.locationChange(aBrowser); |
|
4025 |
|
4026 gBrowser.getNotificationBox(aBrowser).removeTransientNotifications(); |
|
4027 |
|
4028 FullZoom.onLocationChange(aLocationURI, false, aBrowser); |
|
4029 }, |
|
4030 |
|
4031 onRefreshAttempted: function (aBrowser, aWebProgress, aURI, aDelay, aSameURI) { |
|
4032 if (gPrefService.getBoolPref("accessibility.blockautorefresh")) { |
|
4033 let brandBundle = document.getElementById("bundle_brand"); |
|
4034 let brandShortName = brandBundle.getString("brandShortName"); |
|
4035 let refreshButtonText = |
|
4036 gNavigatorBundle.getString("refreshBlocked.goButton"); |
|
4037 let refreshButtonAccesskey = |
|
4038 gNavigatorBundle.getString("refreshBlocked.goButton.accesskey"); |
|
4039 let message = |
|
4040 gNavigatorBundle.getFormattedString(aSameURI ? "refreshBlocked.refreshLabel" |
|
4041 : "refreshBlocked.redirectLabel", |
|
4042 [brandShortName]); |
|
4043 let docShell = aWebProgress.DOMWindow |
|
4044 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
4045 .getInterface(Ci.nsIWebNavigation) |
|
4046 .QueryInterface(Ci.nsIDocShell); |
|
4047 let notificationBox = gBrowser.getNotificationBox(aBrowser); |
|
4048 let notification = notificationBox.getNotificationWithValue("refresh-blocked"); |
|
4049 if (notification) { |
|
4050 notification.label = message; |
|
4051 notification.refreshURI = aURI; |
|
4052 notification.delay = aDelay; |
|
4053 notification.docShell = docShell; |
|
4054 } else { |
|
4055 let buttons = [{ |
|
4056 label: refreshButtonText, |
|
4057 accessKey: refreshButtonAccesskey, |
|
4058 callback: function (aNotification, aButton) { |
|
4059 var refreshURI = aNotification.docShell |
|
4060 .QueryInterface(Ci.nsIRefreshURI); |
|
4061 refreshURI.forceRefreshURI(aNotification.refreshURI, |
|
4062 aNotification.delay, true); |
|
4063 } |
|
4064 }]; |
|
4065 notification = |
|
4066 notificationBox.appendNotification(message, "refresh-blocked", |
|
4067 "chrome://browser/skin/Info.png", |
|
4068 notificationBox.PRIORITY_INFO_MEDIUM, |
|
4069 buttons); |
|
4070 notification.refreshURI = aURI; |
|
4071 notification.delay = aDelay; |
|
4072 notification.docShell = docShell; |
|
4073 } |
|
4074 return false; |
|
4075 } |
|
4076 return true; |
|
4077 } |
|
4078 } |
|
4079 |
|
4080 function nsBrowserAccess() { } |
|
4081 |
|
4082 nsBrowserAccess.prototype = { |
|
4083 QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]), |
|
4084 |
|
4085 _openURIInNewTab: function(aURI, aOpener, aIsExternal) { |
|
4086 let win, needToFocusWin; |
|
4087 |
|
4088 // try the current window. if we're in a popup, fall back on the most recent browser window |
|
4089 if (window.toolbar.visible) |
|
4090 win = window; |
|
4091 else { |
|
4092 let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aOpener || window); |
|
4093 win = RecentWindow.getMostRecentBrowserWindow({private: isPrivate}); |
|
4094 needToFocusWin = true; |
|
4095 } |
|
4096 |
|
4097 if (!win) { |
|
4098 // we couldn't find a suitable window, a new one needs to be opened. |
|
4099 return null; |
|
4100 } |
|
4101 |
|
4102 if (aIsExternal && (!aURI || aURI.spec == "about:blank")) { |
|
4103 win.BrowserOpenTab(); // this also focuses the location bar |
|
4104 win.focus(); |
|
4105 return win.gBrowser.selectedBrowser; |
|
4106 } |
|
4107 |
|
4108 let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"); |
|
4109 let referrer = aOpener ? makeURI(aOpener.location.href) : null; |
|
4110 |
|
4111 let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", { |
|
4112 referrerURI: referrer, |
|
4113 fromExternal: aIsExternal, |
|
4114 inBackground: loadInBackground}); |
|
4115 let browser = win.gBrowser.getBrowserForTab(tab); |
|
4116 |
|
4117 if (needToFocusWin || (!loadInBackground && aIsExternal)) |
|
4118 win.focus(); |
|
4119 |
|
4120 return browser; |
|
4121 }, |
|
4122 |
|
4123 openURI: function (aURI, aOpener, aWhere, aContext) { |
|
4124 var newWindow = null; |
|
4125 var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); |
|
4126 |
|
4127 if (isExternal && aURI && aURI.schemeIs("chrome")) { |
|
4128 dump("use -chrome command-line option to load external chrome urls\n"); |
|
4129 return null; |
|
4130 } |
|
4131 |
|
4132 if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) { |
|
4133 if (isExternal && |
|
4134 gPrefService.prefHasUserValue("browser.link.open_newwindow.override.external")) |
|
4135 aWhere = gPrefService.getIntPref("browser.link.open_newwindow.override.external"); |
|
4136 else |
|
4137 aWhere = gPrefService.getIntPref("browser.link.open_newwindow"); |
|
4138 } |
|
4139 switch (aWhere) { |
|
4140 case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW : |
|
4141 // FIXME: Bug 408379. So how come this doesn't send the |
|
4142 // referrer like the other loads do? |
|
4143 var url = aURI ? aURI.spec : "about:blank"; |
|
4144 // Pass all params to openDialog to ensure that "url" isn't passed through |
|
4145 // loadOneOrMoreURIs, which splits based on "|" |
|
4146 newWindow = openDialog(getBrowserURL(), "_blank", "all,dialog=no", url, null, null, null); |
|
4147 break; |
|
4148 case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB : |
|
4149 let browser = this._openURIInNewTab(aURI, aOpener, isExternal); |
|
4150 if (browser) |
|
4151 newWindow = browser.contentWindow; |
|
4152 break; |
|
4153 default : // OPEN_CURRENTWINDOW or an illegal value |
|
4154 newWindow = content; |
|
4155 if (aURI) { |
|
4156 let referrer = aOpener ? makeURI(aOpener.location.href) : null; |
|
4157 let loadflags = isExternal ? |
|
4158 Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL : |
|
4159 Ci.nsIWebNavigation.LOAD_FLAGS_NONE; |
|
4160 gBrowser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null); |
|
4161 } |
|
4162 if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground")) |
|
4163 window.focus(); |
|
4164 } |
|
4165 return newWindow; |
|
4166 }, |
|
4167 |
|
4168 openURIInFrame: function browser_openURIInFrame(aURI, aOpener, aWhere, aContext) { |
|
4169 if (aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) { |
|
4170 dump("Error: openURIInFrame can only open in new tabs"); |
|
4171 return null; |
|
4172 } |
|
4173 |
|
4174 var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); |
|
4175 let browser = this._openURIInNewTab(aURI, aOpener, isExternal); |
|
4176 if (browser) |
|
4177 return browser.QueryInterface(Ci.nsIFrameLoaderOwner); |
|
4178 |
|
4179 return null; |
|
4180 }, |
|
4181 |
|
4182 isTabContentWindow: function (aWindow) { |
|
4183 return gBrowser.browsers.some(function (browser) browser.contentWindow == aWindow); |
|
4184 }, |
|
4185 |
|
4186 get contentWindow() { |
|
4187 return gBrowser.contentWindow; |
|
4188 } |
|
4189 } |
|
4190 |
|
4191 function getTogglableToolbars() { |
|
4192 let toolbarNodes = Array.slice(gNavToolbox.childNodes); |
|
4193 toolbarNodes = toolbarNodes.concat(gNavToolbox.externalToolbars); |
|
4194 toolbarNodes = toolbarNodes.filter(node => node.getAttribute("toolbarname")); |
|
4195 return toolbarNodes; |
|
4196 } |
|
4197 |
|
4198 function onViewToolbarsPopupShowing(aEvent, aInsertPoint) { |
|
4199 var popup = aEvent.target; |
|
4200 if (popup != aEvent.currentTarget) |
|
4201 return; |
|
4202 |
|
4203 // Empty the menu |
|
4204 for (var i = popup.childNodes.length-1; i >= 0; --i) { |
|
4205 var deadItem = popup.childNodes[i]; |
|
4206 if (deadItem.hasAttribute("toolbarId")) |
|
4207 popup.removeChild(deadItem); |
|
4208 } |
|
4209 |
|
4210 var firstMenuItem = aInsertPoint || popup.firstChild; |
|
4211 |
|
4212 let toolbarNodes = getTogglableToolbars(); |
|
4213 |
|
4214 for (let toolbar of toolbarNodes) { |
|
4215 let menuItem = document.createElement("menuitem"); |
|
4216 let hidingAttribute = toolbar.getAttribute("type") == "menubar" ? |
|
4217 "autohide" : "collapsed"; |
|
4218 menuItem.setAttribute("id", "toggle_" + toolbar.id); |
|
4219 menuItem.setAttribute("toolbarId", toolbar.id); |
|
4220 menuItem.setAttribute("type", "checkbox"); |
|
4221 menuItem.setAttribute("label", toolbar.getAttribute("toolbarname")); |
|
4222 menuItem.setAttribute("checked", toolbar.getAttribute(hidingAttribute) != "true"); |
|
4223 menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey")); |
|
4224 if (popup.id != "toolbar-context-menu") |
|
4225 menuItem.setAttribute("key", toolbar.getAttribute("key")); |
|
4226 |
|
4227 popup.insertBefore(menuItem, firstMenuItem); |
|
4228 |
|
4229 menuItem.addEventListener("command", onViewToolbarCommand, false); |
|
4230 } |
|
4231 |
|
4232 |
|
4233 let moveToPanel = popup.querySelector(".customize-context-moveToPanel"); |
|
4234 let removeFromToolbar = popup.querySelector(".customize-context-removeFromToolbar"); |
|
4235 // View -> Toolbars menu doesn't have the moveToPanel or removeFromToolbar items. |
|
4236 if (!moveToPanel || !removeFromToolbar) { |
|
4237 return; |
|
4238 } |
|
4239 |
|
4240 // triggerNode can be a nested child element of a toolbaritem. |
|
4241 let toolbarItem = popup.triggerNode; |
|
4242 |
|
4243 if (toolbarItem && toolbarItem.localName == "toolbarpaletteitem") { |
|
4244 toolbarItem = toolbarItem.firstChild; |
|
4245 } else if (toolbarItem && toolbarItem.localName != "toolbar") { |
|
4246 while (toolbarItem && toolbarItem.parentNode) { |
|
4247 let parent = toolbarItem.parentNode; |
|
4248 if ((parent.classList && parent.classList.contains("customization-target")) || |
|
4249 parent.getAttribute("overflowfortoolbar") || // Needs to work in the overflow list as well. |
|
4250 parent.localName == "toolbarpaletteitem" || |
|
4251 parent.localName == "toolbar") |
|
4252 break; |
|
4253 toolbarItem = parent; |
|
4254 } |
|
4255 } else { |
|
4256 toolbarItem = null; |
|
4257 } |
|
4258 |
|
4259 // Right-clicking on an empty part of the tabstrip will exit |
|
4260 // the above loop with toolbarItem being the xul:document. |
|
4261 // That has no parentNode, and we should disable the items in |
|
4262 // this case. |
|
4263 let movable = toolbarItem && toolbarItem.parentNode && |
|
4264 CustomizableUI.isWidgetRemovable(toolbarItem); |
|
4265 if (movable) { |
|
4266 moveToPanel.removeAttribute("disabled"); |
|
4267 removeFromToolbar.removeAttribute("disabled"); |
|
4268 } else { |
|
4269 moveToPanel.setAttribute("disabled", true); |
|
4270 removeFromToolbar.setAttribute("disabled", true); |
|
4271 } |
|
4272 } |
|
4273 |
|
4274 function onViewToolbarCommand(aEvent) { |
|
4275 var toolbarId = aEvent.originalTarget.getAttribute("toolbarId"); |
|
4276 var isVisible = aEvent.originalTarget.getAttribute("checked") == "true"; |
|
4277 CustomizableUI.setToolbarVisibility(toolbarId, isVisible); |
|
4278 } |
|
4279 |
|
4280 function setToolbarVisibility(toolbar, isVisible, persist=true) { |
|
4281 let hidingAttribute; |
|
4282 if (toolbar.getAttribute("type") == "menubar") { |
|
4283 hidingAttribute = "autohide"; |
|
4284 #ifdef MOZ_WIDGET_GTK |
|
4285 Services.prefs.setBoolPref("ui.key.menuAccessKeyFocuses", !isVisible); |
|
4286 #endif |
|
4287 } else { |
|
4288 hidingAttribute = "collapsed"; |
|
4289 } |
|
4290 |
|
4291 toolbar.setAttribute(hidingAttribute, !isVisible); |
|
4292 if (persist) { |
|
4293 document.persist(toolbar.id, hidingAttribute); |
|
4294 } |
|
4295 |
|
4296 let eventParams = { |
|
4297 detail: { |
|
4298 visible: isVisible |
|
4299 }, |
|
4300 bubbles: true |
|
4301 }; |
|
4302 let event = new CustomEvent("toolbarvisibilitychange", eventParams); |
|
4303 toolbar.dispatchEvent(event); |
|
4304 |
|
4305 PlacesToolbarHelper.init(); |
|
4306 BookmarkingUI.onToolbarVisibilityChange(); |
|
4307 gBrowser.updateWindowResizers(); |
|
4308 if (isVisible) |
|
4309 ToolbarIconColor.inferFromText(); |
|
4310 } |
|
4311 |
|
4312 var TabsInTitlebar = { |
|
4313 init: function () { |
|
4314 #ifdef CAN_DRAW_IN_TITLEBAR |
|
4315 this._readPref(); |
|
4316 Services.prefs.addObserver(this._prefName, this, false); |
|
4317 |
|
4318 // We need to update the appearance of the titlebar when the menu changes |
|
4319 // from the active to the inactive state. We can't, however, rely on |
|
4320 // DOMMenuBarInactive, because the menu fires this event and then removes |
|
4321 // the inactive attribute after an event-loop spin. |
|
4322 // |
|
4323 // Because updating the appearance involves sampling the heights and margins |
|
4324 // of various elements, it's important that the layout be more or less |
|
4325 // settled before updating the titlebar. So instead of listening to |
|
4326 // DOMMenuBarActive and DOMMenuBarInactive, we use a MutationObserver to |
|
4327 // watch the "invalid" attribute directly. |
|
4328 let menu = document.getElementById("toolbar-menubar"); |
|
4329 this._menuObserver = new MutationObserver(this._onMenuMutate); |
|
4330 this._menuObserver.observe(menu, {attributes: true}); |
|
4331 |
|
4332 this.onAreaReset = function(aArea) { |
|
4333 if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR) |
|
4334 this._update(true); |
|
4335 }; |
|
4336 this.onWidgetAdded = this.onWidgetRemoved = function(aWidgetId, aArea) { |
|
4337 if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR) |
|
4338 this._update(true); |
|
4339 }; |
|
4340 CustomizableUI.addListener(this); |
|
4341 |
|
4342 this._initialized = true; |
|
4343 #endif |
|
4344 }, |
|
4345 |
|
4346 allowedBy: function (condition, allow) { |
|
4347 #ifdef CAN_DRAW_IN_TITLEBAR |
|
4348 if (allow) { |
|
4349 if (condition in this._disallowed) { |
|
4350 delete this._disallowed[condition]; |
|
4351 this._update(true); |
|
4352 } |
|
4353 } else { |
|
4354 if (!(condition in this._disallowed)) { |
|
4355 this._disallowed[condition] = null; |
|
4356 this._update(true); |
|
4357 } |
|
4358 } |
|
4359 #endif |
|
4360 }, |
|
4361 |
|
4362 updateAppearance: function updateAppearance(aForce) { |
|
4363 #ifdef CAN_DRAW_IN_TITLEBAR |
|
4364 this._update(aForce); |
|
4365 #endif |
|
4366 }, |
|
4367 |
|
4368 get enabled() { |
|
4369 return document.documentElement.getAttribute("tabsintitlebar") == "true"; |
|
4370 }, |
|
4371 |
|
4372 #ifdef CAN_DRAW_IN_TITLEBAR |
|
4373 observe: function (subject, topic, data) { |
|
4374 if (topic == "nsPref:changed") |
|
4375 this._readPref(); |
|
4376 }, |
|
4377 |
|
4378 _onMenuMutate: function (aMutations) { |
|
4379 for (let mutation of aMutations) { |
|
4380 if (mutation.attributeName == "inactive" || |
|
4381 mutation.attributeName == "autohide") { |
|
4382 TabsInTitlebar._update(true); |
|
4383 return; |
|
4384 } |
|
4385 } |
|
4386 }, |
|
4387 |
|
4388 _initialized: false, |
|
4389 _disallowed: {}, |
|
4390 _prefName: "browser.tabs.drawInTitlebar", |
|
4391 _lastSizeMode: null, |
|
4392 |
|
4393 _readPref: function () { |
|
4394 this.allowedBy("pref", |
|
4395 Services.prefs.getBoolPref(this._prefName)); |
|
4396 }, |
|
4397 |
|
4398 _update: function (aForce=false) { |
|
4399 function $(id) document.getElementById(id); |
|
4400 function rect(ele) ele.getBoundingClientRect(); |
|
4401 function verticalMargins(cstyle) parseFloat(cstyle.marginBottom) + parseFloat(cstyle.marginTop); |
|
4402 |
|
4403 if (!this._initialized || window.fullScreen) |
|
4404 return; |
|
4405 |
|
4406 let allowed = true; |
|
4407 |
|
4408 if (!aForce) { |
|
4409 // _update is called on resize events, because the window is not ready |
|
4410 // after sizemode events. However, we only care about the event when the |
|
4411 // sizemode is different from the last time we updated the appearance of |
|
4412 // the tabs in the titlebar. |
|
4413 let sizemode = document.documentElement.getAttribute("sizemode"); |
|
4414 if (this._lastSizeMode == sizemode) { |
|
4415 return; |
|
4416 } |
|
4417 this._lastSizeMode = sizemode; |
|
4418 } |
|
4419 |
|
4420 for (let something in this._disallowed) { |
|
4421 allowed = false; |
|
4422 break; |
|
4423 } |
|
4424 |
|
4425 let titlebar = $("titlebar"); |
|
4426 let titlebarContent = $("titlebar-content"); |
|
4427 let menubar = $("toolbar-menubar"); |
|
4428 |
|
4429 if (allowed) { |
|
4430 // We set the tabsintitlebar attribute first so that our CSS for |
|
4431 // tabsintitlebar manifests before we do our measurements. |
|
4432 document.documentElement.setAttribute("tabsintitlebar", "true"); |
|
4433 updateTitlebarDisplay(); |
|
4434 |
|
4435 // Try to avoid reflows in this code by calculating dimensions first and |
|
4436 // then later set the properties affecting layout together in a batch. |
|
4437 |
|
4438 // Get the full height of the tabs toolbar: |
|
4439 let tabsToolbar = $("TabsToolbar"); |
|
4440 let fullTabsHeight = rect(tabsToolbar).height; |
|
4441 // Buttons first: |
|
4442 let captionButtonsBoxWidth = rect($("titlebar-buttonbox-container")).width; |
|
4443 |
|
4444 #ifdef XP_MACOSX |
|
4445 let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width; |
|
4446 // No need to look up the menubar stuff on OS X: |
|
4447 let menuHeight = 0; |
|
4448 let fullMenuHeight = 0; |
|
4449 // Instead, look up the titlebar padding: |
|
4450 let titlebarPadding = parseInt(window.getComputedStyle(titlebar).paddingTop, 10); |
|
4451 #else |
|
4452 // Otherwise, get the height and margins separately for the menubar |
|
4453 let menuHeight = rect(menubar).height; |
|
4454 let menuStyles = window.getComputedStyle(menubar); |
|
4455 let fullMenuHeight = verticalMargins(menuStyles) + menuHeight; |
|
4456 let tabsStyles = window.getComputedStyle(tabsToolbar); |
|
4457 fullTabsHeight += verticalMargins(tabsStyles); |
|
4458 #endif |
|
4459 |
|
4460 // If the navbar overlaps the tabbar using negative margins, we need to take those into |
|
4461 // account so we don't overlap it |
|
4462 let navbarMarginTop = parseFloat(window.getComputedStyle($("nav-bar")).marginTop); |
|
4463 navbarMarginTop = Math.min(navbarMarginTop, 0); |
|
4464 |
|
4465 // And get the height of what's in the titlebar: |
|
4466 let titlebarContentHeight = rect(titlebarContent).height; |
|
4467 |
|
4468 // Begin setting CSS properties which will cause a reflow |
|
4469 |
|
4470 // If the menubar is around (menuHeight is non-zero), try to adjust |
|
4471 // its full height (i.e. including margins) to match the titlebar, |
|
4472 // by changing the menubar's bottom padding |
|
4473 if (menuHeight) { |
|
4474 // Calculate the difference between the titlebar's height and that of the menubar |
|
4475 let menuTitlebarDelta = titlebarContentHeight - fullMenuHeight; |
|
4476 let paddingBottom; |
|
4477 // The titlebar is bigger: |
|
4478 if (menuTitlebarDelta > 0) { |
|
4479 fullMenuHeight += menuTitlebarDelta; |
|
4480 // If there is already padding on the menubar, we need to add that |
|
4481 // to the difference so the total padding is correct: |
|
4482 if ((paddingBottom = menuStyles.paddingBottom)) { |
|
4483 menuTitlebarDelta += parseFloat(paddingBottom); |
|
4484 } |
|
4485 menubar.style.paddingBottom = menuTitlebarDelta + "px"; |
|
4486 // The menubar is bigger, but has bottom padding we can remove: |
|
4487 } else if (menuTitlebarDelta < 0 && (paddingBottom = menuStyles.paddingBottom)) { |
|
4488 let existingPadding = parseFloat(paddingBottom); |
|
4489 // menuTitlebarDelta is negative; work out what's left, but don't set negative padding: |
|
4490 let desiredPadding = Math.max(0, existingPadding + menuTitlebarDelta); |
|
4491 menubar.style.paddingBottom = desiredPadding + "px"; |
|
4492 // We've changed the menu height now: |
|
4493 fullMenuHeight += desiredPadding - existingPadding; |
|
4494 } |
|
4495 } |
|
4496 |
|
4497 // Next, we calculate how much we need to stretch the titlebar down to |
|
4498 // go all the way to the bottom of the tab strip, if necessary. |
|
4499 let tabAndMenuHeight = fullTabsHeight + fullMenuHeight; |
|
4500 |
|
4501 if (tabAndMenuHeight > titlebarContentHeight) { |
|
4502 // We need to increase the titlebar content's outer height (ie including margins) |
|
4503 // to match the tab and menu height: |
|
4504 let extraMargin = tabAndMenuHeight - titlebarContentHeight; |
|
4505 // We need to reduce the height by the amount of navbar overlap |
|
4506 // (this value is 0 or negative): |
|
4507 extraMargin += navbarMarginTop; |
|
4508 // On non-OSX, we can just use bottom margin: |
|
4509 #ifndef XP_MACOSX |
|
4510 titlebarContent.style.marginBottom = extraMargin + "px"; |
|
4511 #endif |
|
4512 titlebarContentHeight += extraMargin; |
|
4513 } |
|
4514 |
|
4515 // Then we bring up the titlebar by the same amount, but we add any negative margin: |
|
4516 titlebar.style.marginBottom = "-" + titlebarContentHeight + "px"; |
|
4517 |
|
4518 |
|
4519 // Finally, size the placeholders: |
|
4520 #ifdef XP_MACOSX |
|
4521 this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth); |
|
4522 #endif |
|
4523 this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth); |
|
4524 |
|
4525 if (!this._draghandles) { |
|
4526 this._draghandles = {}; |
|
4527 let tmp = {}; |
|
4528 Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp); |
|
4529 |
|
4530 let mouseDownCheck = function () { |
|
4531 return !this._dragBindingAlive && TabsInTitlebar.enabled; |
|
4532 }; |
|
4533 |
|
4534 this._draghandles.tabsToolbar = new tmp.WindowDraggingElement(tabsToolbar); |
|
4535 this._draghandles.tabsToolbar.mouseDownCheck = mouseDownCheck; |
|
4536 |
|
4537 this._draghandles.navToolbox = new tmp.WindowDraggingElement(gNavToolbox); |
|
4538 this._draghandles.navToolbox.mouseDownCheck = mouseDownCheck; |
|
4539 } |
|
4540 } else { |
|
4541 document.documentElement.removeAttribute("tabsintitlebar"); |
|
4542 updateTitlebarDisplay(); |
|
4543 |
|
4544 #ifdef XP_MACOSX |
|
4545 let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width; |
|
4546 this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth); |
|
4547 #endif |
|
4548 // Reset the margins and padding that might have been modified: |
|
4549 titlebarContent.style.marginTop = ""; |
|
4550 titlebarContent.style.marginBottom = ""; |
|
4551 titlebar.style.marginBottom = ""; |
|
4552 menubar.style.paddingBottom = ""; |
|
4553 } |
|
4554 |
|
4555 ToolbarIconColor.inferFromText(); |
|
4556 }, |
|
4557 |
|
4558 _sizePlaceholder: function (type, width) { |
|
4559 Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='"+ type +"']"), |
|
4560 function (node) { node.width = width; }); |
|
4561 }, |
|
4562 #endif |
|
4563 |
|
4564 uninit: function () { |
|
4565 #ifdef CAN_DRAW_IN_TITLEBAR |
|
4566 this._initialized = false; |
|
4567 Services.prefs.removeObserver(this._prefName, this); |
|
4568 this._menuObserver.disconnect(); |
|
4569 CustomizableUI.removeListener(this); |
|
4570 #endif |
|
4571 } |
|
4572 }; |
|
4573 |
|
4574 #ifdef CAN_DRAW_IN_TITLEBAR |
|
4575 function updateTitlebarDisplay() { |
|
4576 |
|
4577 #ifdef XP_MACOSX |
|
4578 // OS X and the other platforms differ enough to necessitate this kind of |
|
4579 // special-casing. Like the other platforms where we CAN_DRAW_IN_TITLEBAR, |
|
4580 // we draw in the OS X titlebar when putting the tabs up there. However, OS X |
|
4581 // also draws in the titlebar when a lightweight theme is applied, regardless |
|
4582 // of whether or not the tabs are drawn in the titlebar. |
|
4583 if (TabsInTitlebar.enabled) { |
|
4584 document.documentElement.setAttribute("chromemargin-nonlwtheme", "0,-1,-1,-1"); |
|
4585 document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1"); |
|
4586 document.documentElement.removeAttribute("drawtitle"); |
|
4587 } else { |
|
4588 // We set chromemargin-nonlwtheme to "" instead of removing it as a way of |
|
4589 // making sure that LightweightThemeConsumer doesn't take it upon itself to |
|
4590 // detect this value again if and when we do a lwtheme state change. |
|
4591 document.documentElement.setAttribute("chromemargin-nonlwtheme", ""); |
|
4592 let isCustomizing = document.documentElement.hasAttribute("customizing"); |
|
4593 let hasLWTheme = document.documentElement.hasAttribute("lwtheme"); |
|
4594 let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window); |
|
4595 if ((!hasLWTheme || isCustomizing) && !isPrivate) { |
|
4596 document.documentElement.removeAttribute("chromemargin"); |
|
4597 } |
|
4598 document.documentElement.setAttribute("drawtitle", "true"); |
|
4599 } |
|
4600 |
|
4601 #else |
|
4602 |
|
4603 if (TabsInTitlebar.enabled) |
|
4604 document.documentElement.setAttribute("chromemargin", "0,2,2,2"); |
|
4605 else |
|
4606 document.documentElement.removeAttribute("chromemargin"); |
|
4607 #endif |
|
4608 } |
|
4609 #endif |
|
4610 |
|
4611 #ifdef CAN_DRAW_IN_TITLEBAR |
|
4612 function onTitlebarMaxClick() { |
|
4613 if (window.windowState == window.STATE_MAXIMIZED) |
|
4614 window.restore(); |
|
4615 else |
|
4616 window.maximize(); |
|
4617 } |
|
4618 #endif |
|
4619 |
|
4620 function displaySecurityInfo() |
|
4621 { |
|
4622 BrowserPageInfo(null, "securityTab"); |
|
4623 } |
|
4624 |
|
4625 /** |
|
4626 * Opens or closes the sidebar identified by commandID. |
|
4627 * |
|
4628 * @param commandID a string identifying the sidebar to toggle; see the |
|
4629 * note below. (Optional if a sidebar is already open.) |
|
4630 * @param forceOpen boolean indicating whether the sidebar should be |
|
4631 * opened regardless of its current state (optional). |
|
4632 * @note |
|
4633 * We expect to find a xul:broadcaster element with the specified ID. |
|
4634 * The following attributes on that element may be used and/or modified: |
|
4635 * - id (required) the string to match commandID. The convention |
|
4636 * is to use this naming scheme: 'view<sidebar-name>Sidebar'. |
|
4637 * - sidebarurl (required) specifies the URL to load in this sidebar. |
|
4638 * - sidebartitle or label (in that order) specify the title to |
|
4639 * display on the sidebar. |
|
4640 * - checked indicates whether the sidebar is currently displayed. |
|
4641 * Note that toggleSidebar updates this attribute when |
|
4642 * it changes the sidebar's visibility. |
|
4643 * - group this attribute must be set to "sidebar". |
|
4644 */ |
|
4645 function toggleSidebar(commandID, forceOpen) { |
|
4646 |
|
4647 var sidebarBox = document.getElementById("sidebar-box"); |
|
4648 if (!commandID) |
|
4649 commandID = sidebarBox.getAttribute("sidebarcommand"); |
|
4650 |
|
4651 var sidebarBroadcaster = document.getElementById(commandID); |
|
4652 var sidebar = document.getElementById("sidebar"); // xul:browser |
|
4653 var sidebarTitle = document.getElementById("sidebar-title"); |
|
4654 var sidebarSplitter = document.getElementById("sidebar-splitter"); |
|
4655 |
|
4656 if (sidebarBroadcaster.getAttribute("checked") == "true") { |
|
4657 if (!forceOpen) { |
|
4658 // Replace the document currently displayed in the sidebar with about:blank |
|
4659 // so that we can free memory by unloading the page. We need to explicitly |
|
4660 // create a new content viewer because the old one doesn't get destroyed |
|
4661 // until about:blank has loaded (which does not happen as long as the |
|
4662 // element is hidden). |
|
4663 sidebar.setAttribute("src", "about:blank"); |
|
4664 sidebar.docShell.createAboutBlankContentViewer(null); |
|
4665 |
|
4666 sidebarBroadcaster.removeAttribute("checked"); |
|
4667 sidebarBox.setAttribute("sidebarcommand", ""); |
|
4668 sidebarTitle.value = ""; |
|
4669 sidebarBox.hidden = true; |
|
4670 sidebarSplitter.hidden = true; |
|
4671 gBrowser.selectedBrowser.focus(); |
|
4672 } else { |
|
4673 fireSidebarFocusedEvent(); |
|
4674 } |
|
4675 return; |
|
4676 } |
|
4677 |
|
4678 // now we need to show the specified sidebar |
|
4679 |
|
4680 // ..but first update the 'checked' state of all sidebar broadcasters |
|
4681 var broadcasters = document.getElementsByAttribute("group", "sidebar"); |
|
4682 for (let broadcaster of broadcasters) { |
|
4683 // skip elements that observe sidebar broadcasters and random |
|
4684 // other elements |
|
4685 if (broadcaster.localName != "broadcaster") |
|
4686 continue; |
|
4687 |
|
4688 if (broadcaster != sidebarBroadcaster) |
|
4689 broadcaster.removeAttribute("checked"); |
|
4690 else |
|
4691 sidebarBroadcaster.setAttribute("checked", "true"); |
|
4692 } |
|
4693 |
|
4694 sidebarBox.hidden = false; |
|
4695 sidebarSplitter.hidden = false; |
|
4696 |
|
4697 var url = sidebarBroadcaster.getAttribute("sidebarurl"); |
|
4698 var title = sidebarBroadcaster.getAttribute("sidebartitle"); |
|
4699 if (!title) |
|
4700 title = sidebarBroadcaster.getAttribute("label"); |
|
4701 sidebar.setAttribute("src", url); // kick off async load |
|
4702 sidebarBox.setAttribute("sidebarcommand", sidebarBroadcaster.id); |
|
4703 sidebarTitle.value = title; |
|
4704 |
|
4705 // We set this attribute here in addition to setting it on the <browser> |
|
4706 // element itself, because the code in gBrowserInit.onUnload persists this |
|
4707 // attribute, not the "src" of the <browser id="sidebar">. The reason it |
|
4708 // does that is that we want to delay sidebar load a bit when a browser |
|
4709 // window opens. See delayedStartup(). |
|
4710 sidebarBox.setAttribute("src", url); |
|
4711 |
|
4712 if (sidebar.contentDocument.location.href != url) |
|
4713 sidebar.addEventListener("load", sidebarOnLoad, true); |
|
4714 else // older code handled this case, so we do it too |
|
4715 fireSidebarFocusedEvent(); |
|
4716 } |
|
4717 |
|
4718 function sidebarOnLoad(event) { |
|
4719 var sidebar = document.getElementById("sidebar"); |
|
4720 sidebar.removeEventListener("load", sidebarOnLoad, true); |
|
4721 // We're handling the 'load' event before it bubbles up to the usual |
|
4722 // (non-capturing) event handlers. Let it bubble up before firing the |
|
4723 // SidebarFocused event. |
|
4724 setTimeout(fireSidebarFocusedEvent, 0); |
|
4725 } |
|
4726 |
|
4727 /** |
|
4728 * Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar |
|
4729 * a chance to adjust focus as needed. An additional event is needed, because |
|
4730 * we don't want to focus the sidebar when it's opened on startup or in a new |
|
4731 * window, only when the user opens the sidebar. |
|
4732 */ |
|
4733 function fireSidebarFocusedEvent() { |
|
4734 var sidebar = document.getElementById("sidebar"); |
|
4735 var event = document.createEvent("Events"); |
|
4736 event.initEvent("SidebarFocused", true, false); |
|
4737 sidebar.contentWindow.dispatchEvent(event); |
|
4738 } |
|
4739 |
|
4740 |
|
4741 var gHomeButton = { |
|
4742 prefDomain: "browser.startup.homepage", |
|
4743 observe: function (aSubject, aTopic, aPrefName) |
|
4744 { |
|
4745 if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain) |
|
4746 return; |
|
4747 |
|
4748 this.updateTooltip(); |
|
4749 }, |
|
4750 |
|
4751 updateTooltip: function (homeButton) |
|
4752 { |
|
4753 if (!homeButton) |
|
4754 homeButton = document.getElementById("home-button"); |
|
4755 if (homeButton) { |
|
4756 var homePage = this.getHomePage(); |
|
4757 homePage = homePage.replace(/\|/g,', '); |
|
4758 if (homePage.toLowerCase() == "about:home") |
|
4759 homeButton.setAttribute("tooltiptext", homeButton.getAttribute("aboutHomeOverrideTooltip")); |
|
4760 else |
|
4761 homeButton.setAttribute("tooltiptext", homePage); |
|
4762 } |
|
4763 }, |
|
4764 |
|
4765 getHomePage: function () |
|
4766 { |
|
4767 var url; |
|
4768 try { |
|
4769 url = gPrefService.getComplexValue(this.prefDomain, |
|
4770 Components.interfaces.nsIPrefLocalizedString).data; |
|
4771 } catch (e) { |
|
4772 } |
|
4773 |
|
4774 // use this if we can't find the pref |
|
4775 if (!url) { |
|
4776 var configBundle = Services.strings |
|
4777 .createBundle("chrome://branding/locale/browserconfig.properties"); |
|
4778 url = configBundle.GetStringFromName(this.prefDomain); |
|
4779 } |
|
4780 |
|
4781 return url; |
|
4782 }, |
|
4783 |
|
4784 updatePersonalToolbarStyle: function (homeButton) |
|
4785 { |
|
4786 if (!homeButton) |
|
4787 homeButton = document.getElementById("home-button"); |
|
4788 if (homeButton) |
|
4789 homeButton.className = homeButton.parentNode.id == "PersonalToolbar" |
|
4790 || homeButton.parentNode.parentNode.id == "PersonalToolbar" ? |
|
4791 homeButton.className.replace("toolbarbutton-1", "bookmark-item") : |
|
4792 homeButton.className.replace("bookmark-item", "toolbarbutton-1"); |
|
4793 }, |
|
4794 }; |
|
4795 |
|
4796 const nodeToTooltipMap = { |
|
4797 "bookmarks-menu-button": "bookmarksMenuButton.tooltip", |
|
4798 #ifdef XP_MACOSX |
|
4799 "print-button": "printButton.tooltip", |
|
4800 #endif |
|
4801 "new-window-button": "newWindowButton.tooltip", |
|
4802 "fullscreen-button": "fullscreenButton.tooltip", |
|
4803 "tabview-button": "tabviewButton.tooltip", |
|
4804 }; |
|
4805 const nodeToShortcutMap = { |
|
4806 "bookmarks-menu-button": "manBookmarkKb", |
|
4807 #ifdef XP_MACOSX |
|
4808 "print-button": "printKb", |
|
4809 #endif |
|
4810 "new-window-button": "key_newNavigator", |
|
4811 "fullscreen-button": "key_fullScreen", |
|
4812 "tabview-button": "key_tabview", |
|
4813 }; |
|
4814 const gDynamicTooltipCache = new Map(); |
|
4815 function UpdateDynamicShortcutTooltipText(aTooltip) { |
|
4816 let nodeId = aTooltip.triggerNode.id; |
|
4817 if (!gDynamicTooltipCache.has(nodeId) && nodeId in nodeToTooltipMap) { |
|
4818 let strId = nodeToTooltipMap[nodeId]; |
|
4819 let args = []; |
|
4820 if (nodeId in nodeToShortcutMap) { |
|
4821 let shortcutId = nodeToShortcutMap[nodeId]; |
|
4822 let shortcut = document.getElementById(shortcutId); |
|
4823 if (shortcut) { |
|
4824 args.push(ShortcutUtils.prettifyShortcut(shortcut)); |
|
4825 } |
|
4826 } |
|
4827 gDynamicTooltipCache.set(nodeId, gNavigatorBundle.getFormattedString(strId, args)); |
|
4828 } |
|
4829 aTooltip.setAttribute("label", gDynamicTooltipCache.get(nodeId)); |
|
4830 } |
|
4831 |
|
4832 /** |
|
4833 * Gets the selected text in the active browser. Leading and trailing |
|
4834 * whitespace is removed, and consecutive whitespace is replaced by a single |
|
4835 * space. A maximum of 150 characters will be returned, regardless of the value |
|
4836 * of aCharLen. |
|
4837 * |
|
4838 * @param aCharLen |
|
4839 * The maximum number of characters to return. |
|
4840 */ |
|
4841 function getBrowserSelection(aCharLen) { |
|
4842 // selections of more than 150 characters aren't useful |
|
4843 const kMaxSelectionLen = 150; |
|
4844 const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen); |
|
4845 |
|
4846 let [element, focusedWindow] = BrowserUtils.getFocusSync(document); |
|
4847 var selection = focusedWindow.getSelection().toString(); |
|
4848 // try getting a selected text in text input. |
|
4849 if (!selection) { |
|
4850 var isOnTextInput = function isOnTextInput(elem) { |
|
4851 // we avoid to return a value if a selection is in password field. |
|
4852 // ref. bug 565717 |
|
4853 return elem instanceof HTMLTextAreaElement || |
|
4854 (elem instanceof HTMLInputElement && elem.mozIsTextField(true)); |
|
4855 }; |
|
4856 |
|
4857 if (isOnTextInput(element)) { |
|
4858 selection = element.QueryInterface(Ci.nsIDOMNSEditableElement) |
|
4859 .editor.selection.toString(); |
|
4860 } |
|
4861 } |
|
4862 |
|
4863 if (selection) { |
|
4864 if (selection.length > charLen) { |
|
4865 // only use the first charLen important chars. see bug 221361 |
|
4866 var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}"); |
|
4867 pattern.test(selection); |
|
4868 selection = RegExp.lastMatch; |
|
4869 } |
|
4870 |
|
4871 selection = selection.trim().replace(/\s+/g, " "); |
|
4872 |
|
4873 if (selection.length > charLen) |
|
4874 selection = selection.substr(0, charLen); |
|
4875 } |
|
4876 return selection; |
|
4877 } |
|
4878 |
|
4879 var gWebPanelURI; |
|
4880 function openWebPanel(aTitle, aURI) |
|
4881 { |
|
4882 // Ensure that the web panels sidebar is open. |
|
4883 toggleSidebar('viewWebPanelsSidebar', true); |
|
4884 |
|
4885 // Set the title of the panel. |
|
4886 document.getElementById("sidebar-title").value = aTitle; |
|
4887 |
|
4888 // Tell the Web Panels sidebar to load the bookmark. |
|
4889 var sidebar = document.getElementById("sidebar"); |
|
4890 if (sidebar.docShell && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser')) { |
|
4891 sidebar.contentWindow.loadWebPanel(aURI); |
|
4892 if (gWebPanelURI) { |
|
4893 gWebPanelURI = ""; |
|
4894 sidebar.removeEventListener("load", asyncOpenWebPanel, true); |
|
4895 } |
|
4896 } |
|
4897 else { |
|
4898 // The panel is still being constructed. Attach an onload handler. |
|
4899 if (!gWebPanelURI) |
|
4900 sidebar.addEventListener("load", asyncOpenWebPanel, true); |
|
4901 gWebPanelURI = aURI; |
|
4902 } |
|
4903 } |
|
4904 |
|
4905 function asyncOpenWebPanel(event) |
|
4906 { |
|
4907 var sidebar = document.getElementById("sidebar"); |
|
4908 if (gWebPanelURI && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser')) |
|
4909 sidebar.contentWindow.loadWebPanel(gWebPanelURI); |
|
4910 gWebPanelURI = ""; |
|
4911 sidebar.removeEventListener("load", asyncOpenWebPanel, true); |
|
4912 } |
|
4913 |
|
4914 /* |
|
4915 * - [ Dependencies ] --------------------------------------------------------- |
|
4916 * utilityOverlay.js: |
|
4917 * - gatherTextUnder |
|
4918 */ |
|
4919 |
|
4920 /** |
|
4921 * Extracts linkNode and href for the current click target. |
|
4922 * |
|
4923 * @param event |
|
4924 * The click event. |
|
4925 * @return [href, linkNode]. |
|
4926 * |
|
4927 * @note linkNode will be null if the click wasn't on an anchor |
|
4928 * element (or XLink). |
|
4929 */ |
|
4930 function hrefAndLinkNodeForClickEvent(event) |
|
4931 { |
|
4932 function isHTMLLink(aNode) |
|
4933 { |
|
4934 // Be consistent with what nsContextMenu.js does. |
|
4935 return ((aNode instanceof HTMLAnchorElement && aNode.href) || |
|
4936 (aNode instanceof HTMLAreaElement && aNode.href) || |
|
4937 aNode instanceof HTMLLinkElement); |
|
4938 } |
|
4939 |
|
4940 let node = event.target; |
|
4941 while (node && !isHTMLLink(node)) { |
|
4942 node = node.parentNode; |
|
4943 } |
|
4944 |
|
4945 if (node) |
|
4946 return [node.href, node]; |
|
4947 |
|
4948 // If there is no linkNode, try simple XLink. |
|
4949 let href, baseURI; |
|
4950 node = event.target; |
|
4951 while (node && !href) { |
|
4952 if (node.nodeType == Node.ELEMENT_NODE) { |
|
4953 href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href"); |
|
4954 if (href) |
|
4955 baseURI = node.baseURI; |
|
4956 } |
|
4957 node = node.parentNode; |
|
4958 } |
|
4959 |
|
4960 // In case of XLink, we don't return the node we got href from since |
|
4961 // callers expect <a>-like elements. |
|
4962 return [href ? makeURLAbsolute(baseURI, href) : null, null]; |
|
4963 } |
|
4964 |
|
4965 /** |
|
4966 * Called whenever the user clicks in the content area. |
|
4967 * |
|
4968 * @param event |
|
4969 * The click event. |
|
4970 * @param isPanelClick |
|
4971 * Whether the event comes from a web panel. |
|
4972 * @note default event is prevented if the click is handled. |
|
4973 */ |
|
4974 function contentAreaClick(event, isPanelClick) |
|
4975 { |
|
4976 if (!event.isTrusted || event.defaultPrevented || event.button == 2) |
|
4977 return; |
|
4978 |
|
4979 let [href, linkNode] = hrefAndLinkNodeForClickEvent(event); |
|
4980 if (!href) { |
|
4981 // Not a link, handle middle mouse navigation. |
|
4982 if (event.button == 1 && |
|
4983 gPrefService.getBoolPref("middlemouse.contentLoadURL") && |
|
4984 !gPrefService.getBoolPref("general.autoScroll")) { |
|
4985 middleMousePaste(event); |
|
4986 event.preventDefault(); |
|
4987 } |
|
4988 return; |
|
4989 } |
|
4990 |
|
4991 // This code only applies if we have a linkNode (i.e. clicks on real anchor |
|
4992 // elements, as opposed to XLink). |
|
4993 if (linkNode && event.button == 0 && |
|
4994 !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) { |
|
4995 // A Web panel's links should target the main content area. Do this |
|
4996 // if no modifier keys are down and if there's no target or the target |
|
4997 // equals _main (the IE convention) or _content (the Mozilla convention). |
|
4998 let target = linkNode.target; |
|
4999 let mainTarget = !target || target == "_content" || target == "_main"; |
|
5000 if (isPanelClick && mainTarget) { |
|
5001 // javascript and data links should be executed in the current browser. |
|
5002 if (linkNode.getAttribute("onclick") || |
|
5003 href.startsWith("javascript:") || |
|
5004 href.startsWith("data:")) |
|
5005 return; |
|
5006 |
|
5007 try { |
|
5008 urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal); |
|
5009 } |
|
5010 catch(ex) { |
|
5011 // Prevent loading unsecure destinations. |
|
5012 event.preventDefault(); |
|
5013 return; |
|
5014 } |
|
5015 |
|
5016 loadURI(href, null, null, false); |
|
5017 event.preventDefault(); |
|
5018 return; |
|
5019 } |
|
5020 |
|
5021 if (linkNode.getAttribute("rel") == "sidebar") { |
|
5022 // This is the Opera convention for a special link that, when clicked, |
|
5023 // allows to add a sidebar panel. The link's title attribute contains |
|
5024 // the title that should be used for the sidebar panel. |
|
5025 PlacesUIUtils.showBookmarkDialog({ action: "add" |
|
5026 , type: "bookmark" |
|
5027 , uri: makeURI(href) |
|
5028 , title: linkNode.getAttribute("title") |
|
5029 , loadBookmarkInSidebar: true |
|
5030 , hiddenRows: [ "description" |
|
5031 , "location" |
|
5032 , "keyword" ] |
|
5033 }, window); |
|
5034 event.preventDefault(); |
|
5035 return; |
|
5036 } |
|
5037 } |
|
5038 |
|
5039 handleLinkClick(event, href, linkNode); |
|
5040 |
|
5041 // Mark the page as a user followed link. This is done so that history can |
|
5042 // distinguish automatic embed visits from user activated ones. For example |
|
5043 // pages loaded in frames are embed visits and lost with the session, while |
|
5044 // visits across frames should be preserved. |
|
5045 try { |
|
5046 if (!PrivateBrowsingUtils.isWindowPrivate(window)) |
|
5047 PlacesUIUtils.markPageAsFollowedLink(href); |
|
5048 } catch (ex) { /* Skip invalid URIs. */ } |
|
5049 } |
|
5050 |
|
5051 /** |
|
5052 * Handles clicks on links. |
|
5053 * |
|
5054 * @return true if the click event was handled, false otherwise. |
|
5055 */ |
|
5056 function handleLinkClick(event, href, linkNode) { |
|
5057 if (event.button == 2) // right click |
|
5058 return false; |
|
5059 |
|
5060 var where = whereToOpenLink(event); |
|
5061 if (where == "current") |
|
5062 return false; |
|
5063 |
|
5064 var doc = event.target.ownerDocument; |
|
5065 |
|
5066 if (where == "save") { |
|
5067 saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true, |
|
5068 true, doc.documentURIObject, doc); |
|
5069 event.preventDefault(); |
|
5070 return true; |
|
5071 } |
|
5072 |
|
5073 var referrerURI = doc.documentURIObject; |
|
5074 // if the mixedContentChannel is present and the referring URI passes |
|
5075 // a same origin check with the target URI, we can preserve the users |
|
5076 // decision of disabling MCB on a page for it's child tabs. |
|
5077 var persistDisableMCBInChildTab = false; |
|
5078 |
|
5079 if (where == "tab" && gBrowser.docShell.mixedContentChannel) { |
|
5080 const sm = Services.scriptSecurityManager; |
|
5081 try { |
|
5082 var targetURI = makeURI(href); |
|
5083 sm.checkSameOriginURI(referrerURI, targetURI, false); |
|
5084 persistDisableMCBInChildTab = true; |
|
5085 } |
|
5086 catch (e) { } |
|
5087 } |
|
5088 |
|
5089 urlSecurityCheck(href, doc.nodePrincipal); |
|
5090 openLinkIn(href, where, { referrerURI: referrerURI, |
|
5091 charset: doc.characterSet, |
|
5092 disableMCB: persistDisableMCBInChildTab}); |
|
5093 event.preventDefault(); |
|
5094 return true; |
|
5095 } |
|
5096 |
|
5097 function middleMousePaste(event) { |
|
5098 let clipboard = readFromClipboard(); |
|
5099 if (!clipboard) |
|
5100 return; |
|
5101 |
|
5102 // Strip embedded newlines and surrounding whitespace, to match the URL |
|
5103 // bar's behavior (stripsurroundingwhitespace) |
|
5104 clipboard = clipboard.replace(/\s*\n\s*/g, ""); |
|
5105 |
|
5106 // if it's not the current tab, we don't need to do anything because the |
|
5107 // browser doesn't exist. |
|
5108 let where = whereToOpenLink(event, true, false); |
|
5109 let lastLocationChange; |
|
5110 if (where == "current") { |
|
5111 lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; |
|
5112 } |
|
5113 |
|
5114 getShortcutOrURIAndPostData(clipboard, data => { |
|
5115 try { |
|
5116 makeURI(data.url); |
|
5117 } catch (ex) { |
|
5118 // Not a valid URI. |
|
5119 return; |
|
5120 } |
|
5121 |
|
5122 try { |
|
5123 addToUrlbarHistory(data.url); |
|
5124 } catch (ex) { |
|
5125 // Things may go wrong when adding url to session history, |
|
5126 // but don't let that interfere with the loading of the url. |
|
5127 Cu.reportError(ex); |
|
5128 } |
|
5129 |
|
5130 if (where != "current" || |
|
5131 lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) { |
|
5132 openUILink(data.url, event, |
|
5133 { ignoreButton: true, |
|
5134 disallowInheritPrincipal: !data.mayInheritPrincipal }); |
|
5135 } |
|
5136 }); |
|
5137 |
|
5138 event.stopPropagation(); |
|
5139 } |
|
5140 |
|
5141 function handleDroppedLink(event, url, name) |
|
5142 { |
|
5143 let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; |
|
5144 |
|
5145 getShortcutOrURIAndPostData(url, data => { |
|
5146 if (data.url && |
|
5147 lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) |
|
5148 loadURI(data.url, null, data.postData, false); |
|
5149 }); |
|
5150 |
|
5151 // Keep the event from being handled by the dragDrop listeners |
|
5152 // built-in to gecko if they happen to be above us. |
|
5153 event.preventDefault(); |
|
5154 }; |
|
5155 |
|
5156 function BrowserSetForcedCharacterSet(aCharset) |
|
5157 { |
|
5158 if (aCharset) { |
|
5159 gBrowser.docShell.gatherCharsetMenuTelemetry(); |
|
5160 gBrowser.docShell.charset = aCharset; |
|
5161 // Save the forced character-set |
|
5162 if (!PrivateBrowsingUtils.isWindowPrivate(window)) |
|
5163 PlacesUtils.setCharsetForURI(getWebNavigation().currentURI, aCharset); |
|
5164 } |
|
5165 BrowserCharsetReload(); |
|
5166 } |
|
5167 |
|
5168 function BrowserCharsetReload() |
|
5169 { |
|
5170 BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE); |
|
5171 } |
|
5172 |
|
5173 function charsetMenuGetElement(parent, charset) { |
|
5174 return parent.getElementsByAttribute("charset", charset)[0]; |
|
5175 } |
|
5176 |
|
5177 function UpdateCurrentCharset(target) { |
|
5178 // extract the charset from DOM |
|
5179 var wnd = document.commandDispatcher.focusedWindow; |
|
5180 if ((window == wnd) || (wnd == null)) wnd = window.content; |
|
5181 |
|
5182 // Uncheck previous item |
|
5183 if (gPrevCharset) { |
|
5184 var pref_item = charsetMenuGetElement(target, gPrevCharset); |
|
5185 if (pref_item) |
|
5186 pref_item.setAttribute('checked', 'false'); |
|
5187 } |
|
5188 |
|
5189 var menuitem = charsetMenuGetElement(target, CharsetMenu.foldCharset(wnd.document.characterSet)); |
|
5190 if (menuitem) { |
|
5191 menuitem.setAttribute('checked', 'true'); |
|
5192 } |
|
5193 } |
|
5194 |
|
5195 function charsetLoadListener() { |
|
5196 var charset = CharsetMenu.foldCharset(window.content.document.characterSet); |
|
5197 |
|
5198 if (charset.length > 0 && (charset != gLastBrowserCharset)) { |
|
5199 gPrevCharset = gLastBrowserCharset; |
|
5200 gLastBrowserCharset = charset; |
|
5201 } |
|
5202 } |
|
5203 |
|
5204 var gPageStyleMenu = { |
|
5205 |
|
5206 // This maps from a <browser> element (or, more specifically, a |
|
5207 // browser's permanentKey) to a CPOW that gives synchronous access |
|
5208 // to the list of style sheets in a content window. The use of the |
|
5209 // permanentKey is to avoid issues with docshell swapping. |
|
5210 _pageStyleSyncHandlers: new WeakMap(), |
|
5211 |
|
5212 init: function() { |
|
5213 let mm = window.messageManager; |
|
5214 mm.addMessageListener("PageStyle:SetSyncHandler", (msg) => { |
|
5215 this._pageStyleSyncHandlers.set(msg.target.permanentKey, msg.objects.syncHandler); |
|
5216 }); |
|
5217 }, |
|
5218 |
|
5219 getAllStyleSheets: function () { |
|
5220 let handler = this._pageStyleSyncHandlers.get(gBrowser.selectedBrowser.permanentKey); |
|
5221 try { |
|
5222 return handler.getAllStyleSheets(); |
|
5223 } catch (ex) { |
|
5224 // In case the child died or timed out. |
|
5225 return []; |
|
5226 } |
|
5227 }, |
|
5228 |
|
5229 _getStyleSheetInfo: function (browser) { |
|
5230 let handler = this._pageStyleSyncHandlers.get(gBrowser.selectedBrowser.permanentKey); |
|
5231 try { |
|
5232 return handler.getStyleSheetInfo(); |
|
5233 } catch (ex) { |
|
5234 // In case the child died or timed out. |
|
5235 return {styleSheets: [], authorStyleDisabled: false, preferredStyleSheetSet: true}; |
|
5236 } |
|
5237 }, |
|
5238 |
|
5239 fillPopup: function (menuPopup) { |
|
5240 let styleSheetInfo = this._getStyleSheetInfo(gBrowser.selectedBrowser); |
|
5241 var noStyle = menuPopup.firstChild; |
|
5242 var persistentOnly = noStyle.nextSibling; |
|
5243 var sep = persistentOnly.nextSibling; |
|
5244 while (sep.nextSibling) |
|
5245 menuPopup.removeChild(sep.nextSibling); |
|
5246 |
|
5247 let styleSheets = styleSheetInfo.styleSheets; |
|
5248 var currentStyleSheets = {}; |
|
5249 var styleDisabled = styleSheetInfo.authorStyleDisabled; |
|
5250 var haveAltSheets = false; |
|
5251 var altStyleSelected = false; |
|
5252 |
|
5253 for (let currentStyleSheet of styleSheets) { |
|
5254 if (!currentStyleSheet.disabled) |
|
5255 altStyleSelected = true; |
|
5256 |
|
5257 haveAltSheets = true; |
|
5258 |
|
5259 let lastWithSameTitle = null; |
|
5260 if (currentStyleSheet.title in currentStyleSheets) |
|
5261 lastWithSameTitle = currentStyleSheets[currentStyleSheet.title]; |
|
5262 |
|
5263 if (!lastWithSameTitle) { |
|
5264 let menuItem = document.createElement("menuitem"); |
|
5265 menuItem.setAttribute("type", "radio"); |
|
5266 menuItem.setAttribute("label", currentStyleSheet.title); |
|
5267 menuItem.setAttribute("data", currentStyleSheet.title); |
|
5268 menuItem.setAttribute("checked", !currentStyleSheet.disabled && !styleDisabled); |
|
5269 menuItem.setAttribute("oncommand", "gPageStyleMenu.switchStyleSheet(this.getAttribute('data'));"); |
|
5270 menuPopup.appendChild(menuItem); |
|
5271 currentStyleSheets[currentStyleSheet.title] = menuItem; |
|
5272 } else if (currentStyleSheet.disabled) { |
|
5273 lastWithSameTitle.removeAttribute("checked"); |
|
5274 } |
|
5275 } |
|
5276 |
|
5277 noStyle.setAttribute("checked", styleDisabled); |
|
5278 persistentOnly.setAttribute("checked", !altStyleSelected && !styleDisabled); |
|
5279 persistentOnly.hidden = styleSheetInfo.preferredStyleSheetSet ? haveAltSheets : false; |
|
5280 sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets; |
|
5281 }, |
|
5282 |
|
5283 switchStyleSheet: function (title) { |
|
5284 let mm = gBrowser.selectedBrowser.messageManager; |
|
5285 mm.sendAsyncMessage("PageStyle:Switch", {title: title}); |
|
5286 }, |
|
5287 |
|
5288 disableStyle: function () { |
|
5289 let mm = gBrowser.selectedBrowser.messageManager; |
|
5290 mm.sendAsyncMessage("PageStyle:Disable"); |
|
5291 }, |
|
5292 }; |
|
5293 |
|
5294 /* Legacy global page-style functions */ |
|
5295 var getAllStyleSheets = gPageStyleMenu.getAllStyleSheets.bind(gPageStyleMenu); |
|
5296 var stylesheetFillPopup = gPageStyleMenu.fillPopup.bind(gPageStyleMenu); |
|
5297 function stylesheetSwitchAll(contentWindow, title) { |
|
5298 // We ignore the contentWindow param. Add-ons don't appear to use |
|
5299 // it, and it's difficult to support in e10s (where it will be a |
|
5300 // CPOW). |
|
5301 gPageStyleMenu.switchStyleSheet(title); |
|
5302 } |
|
5303 function setStyleDisabled(disabled) { |
|
5304 if (disabled) |
|
5305 gPageStyleMenu.disableStyle(); |
|
5306 } |
|
5307 |
|
5308 |
|
5309 var LanguageDetectionListener = { |
|
5310 init: function() { |
|
5311 window.messageManager.addMessageListener("LanguageDetection:Result", msg => { |
|
5312 Translation.languageDetected(msg.target, msg.data); |
|
5313 }); |
|
5314 } |
|
5315 }; |
|
5316 |
|
5317 |
|
5318 var BrowserOffline = { |
|
5319 _inited: false, |
|
5320 |
|
5321 ///////////////////////////////////////////////////////////////////////////// |
|
5322 // BrowserOffline Public Methods |
|
5323 init: function () |
|
5324 { |
|
5325 if (!this._uiElement) |
|
5326 this._uiElement = document.getElementById("workOfflineMenuitemState"); |
|
5327 |
|
5328 Services.obs.addObserver(this, "network:offline-status-changed", false); |
|
5329 |
|
5330 this._updateOfflineUI(Services.io.offline); |
|
5331 |
|
5332 this._inited = true; |
|
5333 }, |
|
5334 |
|
5335 uninit: function () |
|
5336 { |
|
5337 if (this._inited) { |
|
5338 Services.obs.removeObserver(this, "network:offline-status-changed"); |
|
5339 } |
|
5340 }, |
|
5341 |
|
5342 toggleOfflineStatus: function () |
|
5343 { |
|
5344 var ioService = Services.io; |
|
5345 |
|
5346 // Stop automatic management of the offline status |
|
5347 try { |
|
5348 ioService.manageOfflineStatus = false; |
|
5349 } catch (ex) { |
|
5350 } |
|
5351 |
|
5352 if (!ioService.offline && !this._canGoOffline()) { |
|
5353 this._updateOfflineUI(false); |
|
5354 return; |
|
5355 } |
|
5356 |
|
5357 ioService.offline = !ioService.offline; |
|
5358 }, |
|
5359 |
|
5360 ///////////////////////////////////////////////////////////////////////////// |
|
5361 // nsIObserver |
|
5362 observe: function (aSubject, aTopic, aState) |
|
5363 { |
|
5364 if (aTopic != "network:offline-status-changed") |
|
5365 return; |
|
5366 |
|
5367 this._updateOfflineUI(aState == "offline"); |
|
5368 }, |
|
5369 |
|
5370 ///////////////////////////////////////////////////////////////////////////// |
|
5371 // BrowserOffline Implementation Methods |
|
5372 _canGoOffline: function () |
|
5373 { |
|
5374 try { |
|
5375 var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); |
|
5376 Services.obs.notifyObservers(cancelGoOffline, "offline-requested", null); |
|
5377 |
|
5378 // Something aborted the quit process. |
|
5379 if (cancelGoOffline.data) |
|
5380 return false; |
|
5381 } |
|
5382 catch (ex) { |
|
5383 } |
|
5384 |
|
5385 return true; |
|
5386 }, |
|
5387 |
|
5388 _uiElement: null, |
|
5389 _updateOfflineUI: function (aOffline) |
|
5390 { |
|
5391 var offlineLocked = gPrefService.prefIsLocked("network.online"); |
|
5392 if (offlineLocked) |
|
5393 this._uiElement.setAttribute("disabled", "true"); |
|
5394 |
|
5395 this._uiElement.setAttribute("checked", aOffline); |
|
5396 } |
|
5397 }; |
|
5398 |
|
5399 var OfflineApps = { |
|
5400 ///////////////////////////////////////////////////////////////////////////// |
|
5401 // OfflineApps Public Methods |
|
5402 init: function () |
|
5403 { |
|
5404 Services.obs.addObserver(this, "offline-cache-update-completed", false); |
|
5405 }, |
|
5406 |
|
5407 uninit: function () |
|
5408 { |
|
5409 Services.obs.removeObserver(this, "offline-cache-update-completed"); |
|
5410 }, |
|
5411 |
|
5412 handleEvent: function(event) { |
|
5413 if (event.type == "MozApplicationManifest") { |
|
5414 this.offlineAppRequested(event.originalTarget.defaultView); |
|
5415 } |
|
5416 }, |
|
5417 |
|
5418 ///////////////////////////////////////////////////////////////////////////// |
|
5419 // OfflineApps Implementation Methods |
|
5420 |
|
5421 // XXX: _getBrowserWindowForContentWindow and _getBrowserForContentWindow |
|
5422 // were taken from browser/components/feeds/src/WebContentConverter. |
|
5423 _getBrowserWindowForContentWindow: function(aContentWindow) { |
|
5424 return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
|
5425 .getInterface(Ci.nsIWebNavigation) |
|
5426 .QueryInterface(Ci.nsIDocShellTreeItem) |
|
5427 .rootTreeItem |
|
5428 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
5429 .getInterface(Ci.nsIDOMWindow) |
|
5430 .wrappedJSObject; |
|
5431 }, |
|
5432 |
|
5433 _getBrowserForContentWindow: function(aBrowserWindow, aContentWindow) { |
|
5434 // This depends on pseudo APIs of browser.js and tabbrowser.xml |
|
5435 aContentWindow = aContentWindow.top; |
|
5436 var browsers = aBrowserWindow.gBrowser.browsers; |
|
5437 for (let browser of browsers) { |
|
5438 if (browser.contentWindow == aContentWindow) |
|
5439 return browser; |
|
5440 } |
|
5441 // handle other browser/iframe elements that may need popupnotifications |
|
5442 let browser = aContentWindow |
|
5443 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
5444 .getInterface(Ci.nsIWebNavigation) |
|
5445 .QueryInterface(Ci.nsIDocShell) |
|
5446 .chromeEventHandler; |
|
5447 if (browser.getAttribute("popupnotificationanchor")) |
|
5448 return browser; |
|
5449 return null; |
|
5450 }, |
|
5451 |
|
5452 _getManifestURI: function(aWindow) { |
|
5453 if (!aWindow.document.documentElement) |
|
5454 return null; |
|
5455 |
|
5456 var attr = aWindow.document.documentElement.getAttribute("manifest"); |
|
5457 if (!attr) |
|
5458 return null; |
|
5459 |
|
5460 try { |
|
5461 var contentURI = makeURI(aWindow.location.href, null, null); |
|
5462 return makeURI(attr, aWindow.document.characterSet, contentURI); |
|
5463 } catch (e) { |
|
5464 return null; |
|
5465 } |
|
5466 }, |
|
5467 |
|
5468 // A cache update isn't tied to a specific window. Try to find |
|
5469 // the best browser in which to warn the user about space usage |
|
5470 _getBrowserForCacheUpdate: function(aCacheUpdate) { |
|
5471 // Prefer the current browser |
|
5472 var uri = this._getManifestURI(content); |
|
5473 if (uri && uri.equals(aCacheUpdate.manifestURI)) { |
|
5474 return gBrowser.selectedBrowser; |
|
5475 } |
|
5476 |
|
5477 var browsers = gBrowser.browsers; |
|
5478 for (let browser of browsers) { |
|
5479 uri = this._getManifestURI(browser.contentWindow); |
|
5480 if (uri && uri.equals(aCacheUpdate.manifestURI)) { |
|
5481 return browser; |
|
5482 } |
|
5483 } |
|
5484 |
|
5485 // is this from a non-tab browser/iframe? |
|
5486 browsers = document.querySelectorAll("iframe[popupnotificationanchor] | browser[popupnotificationanchor]"); |
|
5487 for (let browser of browsers) { |
|
5488 uri = this._getManifestURI(browser.contentWindow); |
|
5489 if (uri && uri.equals(aCacheUpdate.manifestURI)) { |
|
5490 return browser; |
|
5491 } |
|
5492 } |
|
5493 |
|
5494 return null; |
|
5495 }, |
|
5496 |
|
5497 _warnUsage: function(aBrowser, aURI) { |
|
5498 if (!aBrowser) |
|
5499 return; |
|
5500 |
|
5501 let mainAction = { |
|
5502 label: gNavigatorBundle.getString("offlineApps.manageUsage"), |
|
5503 accessKey: gNavigatorBundle.getString("offlineApps.manageUsageAccessKey"), |
|
5504 callback: OfflineApps.manage |
|
5505 }; |
|
5506 |
|
5507 let warnQuota = gPrefService.getIntPref("offline-apps.quota.warn"); |
|
5508 let message = gNavigatorBundle.getFormattedString("offlineApps.usage", |
|
5509 [ aURI.host, |
|
5510 warnQuota / 1024 ]); |
|
5511 |
|
5512 let anchorID = "indexedDB-notification-icon"; |
|
5513 PopupNotifications.show(aBrowser, "offline-app-usage", message, |
|
5514 anchorID, mainAction); |
|
5515 |
|
5516 // Now that we've warned once, prevent the warning from showing up |
|
5517 // again. |
|
5518 Services.perms.add(aURI, "offline-app", |
|
5519 Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN); |
|
5520 }, |
|
5521 |
|
5522 // XXX: duplicated in preferences/advanced.js |
|
5523 _getOfflineAppUsage: function (host, groups) |
|
5524 { |
|
5525 var cacheService = Cc["@mozilla.org/network/application-cache-service;1"]. |
|
5526 getService(Ci.nsIApplicationCacheService); |
|
5527 if (!groups) |
|
5528 groups = cacheService.getGroups(); |
|
5529 |
|
5530 var usage = 0; |
|
5531 for (let group of groups) { |
|
5532 var uri = Services.io.newURI(group, null, null); |
|
5533 if (uri.asciiHost == host) { |
|
5534 var cache = cacheService.getActiveCache(group); |
|
5535 usage += cache.usage; |
|
5536 } |
|
5537 } |
|
5538 |
|
5539 return usage; |
|
5540 }, |
|
5541 |
|
5542 _checkUsage: function(aURI) { |
|
5543 // if the user has already allowed excessive usage, don't bother checking |
|
5544 if (Services.perms.testExactPermission(aURI, "offline-app") != |
|
5545 Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN) { |
|
5546 var usage = this._getOfflineAppUsage(aURI.asciiHost); |
|
5547 var warnQuota = gPrefService.getIntPref("offline-apps.quota.warn"); |
|
5548 if (usage >= warnQuota * 1024) { |
|
5549 return true; |
|
5550 } |
|
5551 } |
|
5552 |
|
5553 return false; |
|
5554 }, |
|
5555 |
|
5556 offlineAppRequested: function(aContentWindow) { |
|
5557 if (!gPrefService.getBoolPref("browser.offline-apps.notify")) { |
|
5558 return; |
|
5559 } |
|
5560 |
|
5561 let browserWindow = this._getBrowserWindowForContentWindow(aContentWindow); |
|
5562 let browser = this._getBrowserForContentWindow(browserWindow, |
|
5563 aContentWindow); |
|
5564 |
|
5565 let currentURI = aContentWindow.document.documentURIObject; |
|
5566 |
|
5567 // don't bother showing UI if the user has already made a decision |
|
5568 if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION) |
|
5569 return; |
|
5570 |
|
5571 try { |
|
5572 if (gPrefService.getBoolPref("offline-apps.allow_by_default")) { |
|
5573 // all pages can use offline capabilities, no need to ask the user |
|
5574 return; |
|
5575 } |
|
5576 } catch(e) { |
|
5577 // this pref isn't set by default, ignore failures |
|
5578 } |
|
5579 |
|
5580 let host = currentURI.asciiHost; |
|
5581 let notificationID = "offline-app-requested-" + host; |
|
5582 let notification = PopupNotifications.getNotification(notificationID, browser); |
|
5583 |
|
5584 if (notification) { |
|
5585 notification.options.documents.push(aContentWindow.document); |
|
5586 } else { |
|
5587 let mainAction = { |
|
5588 label: gNavigatorBundle.getString("offlineApps.allow"), |
|
5589 accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"), |
|
5590 callback: function() { |
|
5591 for (let document of notification.options.documents) { |
|
5592 OfflineApps.allowSite(document); |
|
5593 } |
|
5594 } |
|
5595 }; |
|
5596 let secondaryActions = [{ |
|
5597 label: gNavigatorBundle.getString("offlineApps.never"), |
|
5598 accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"), |
|
5599 callback: function() { |
|
5600 for (let document of notification.options.documents) { |
|
5601 OfflineApps.disallowSite(document); |
|
5602 } |
|
5603 } |
|
5604 }]; |
|
5605 let message = gNavigatorBundle.getFormattedString("offlineApps.available", |
|
5606 [ host ]); |
|
5607 let anchorID = "indexedDB-notification-icon"; |
|
5608 let options= { |
|
5609 documents : [ aContentWindow.document ] |
|
5610 }; |
|
5611 notification = PopupNotifications.show(browser, notificationID, message, |
|
5612 anchorID, mainAction, |
|
5613 secondaryActions, options); |
|
5614 } |
|
5615 }, |
|
5616 |
|
5617 allowSite: function(aDocument) { |
|
5618 Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.ALLOW_ACTION); |
|
5619 |
|
5620 // When a site is enabled while loading, manifest resources will |
|
5621 // start fetching immediately. This one time we need to do it |
|
5622 // ourselves. |
|
5623 this._startFetching(aDocument); |
|
5624 }, |
|
5625 |
|
5626 disallowSite: function(aDocument) { |
|
5627 Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.DENY_ACTION); |
|
5628 }, |
|
5629 |
|
5630 manage: function() { |
|
5631 openAdvancedPreferences("networkTab"); |
|
5632 }, |
|
5633 |
|
5634 _startFetching: function(aDocument) { |
|
5635 if (!aDocument.documentElement) |
|
5636 return; |
|
5637 |
|
5638 var manifest = aDocument.documentElement.getAttribute("manifest"); |
|
5639 if (!manifest) |
|
5640 return; |
|
5641 |
|
5642 var manifestURI = makeURI(manifest, aDocument.characterSet, |
|
5643 aDocument.documentURIObject); |
|
5644 |
|
5645 var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"]. |
|
5646 getService(Ci.nsIOfflineCacheUpdateService); |
|
5647 updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject, window); |
|
5648 }, |
|
5649 |
|
5650 ///////////////////////////////////////////////////////////////////////////// |
|
5651 // nsIObserver |
|
5652 observe: function (aSubject, aTopic, aState) |
|
5653 { |
|
5654 if (aTopic == "offline-cache-update-completed") { |
|
5655 var cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate); |
|
5656 |
|
5657 var uri = cacheUpdate.manifestURI; |
|
5658 if (OfflineApps._checkUsage(uri)) { |
|
5659 var browser = this._getBrowserForCacheUpdate(cacheUpdate); |
|
5660 if (browser) { |
|
5661 OfflineApps._warnUsage(browser, cacheUpdate.manifestURI); |
|
5662 } |
|
5663 } |
|
5664 } |
|
5665 } |
|
5666 }; |
|
5667 |
|
5668 var IndexedDBPromptHelper = { |
|
5669 _permissionsPrompt: "indexedDB-permissions-prompt", |
|
5670 _permissionsResponse: "indexedDB-permissions-response", |
|
5671 |
|
5672 _quotaPrompt: "indexedDB-quota-prompt", |
|
5673 _quotaResponse: "indexedDB-quota-response", |
|
5674 _quotaCancel: "indexedDB-quota-cancel", |
|
5675 |
|
5676 _notificationIcon: "indexedDB-notification-icon", |
|
5677 |
|
5678 init: |
|
5679 function IndexedDBPromptHelper_init() { |
|
5680 Services.obs.addObserver(this, this._permissionsPrompt, false); |
|
5681 Services.obs.addObserver(this, this._quotaPrompt, false); |
|
5682 Services.obs.addObserver(this, this._quotaCancel, false); |
|
5683 }, |
|
5684 |
|
5685 uninit: |
|
5686 function IndexedDBPromptHelper_uninit() { |
|
5687 Services.obs.removeObserver(this, this._permissionsPrompt); |
|
5688 Services.obs.removeObserver(this, this._quotaPrompt); |
|
5689 Services.obs.removeObserver(this, this._quotaCancel); |
|
5690 }, |
|
5691 |
|
5692 observe: |
|
5693 function IndexedDBPromptHelper_observe(subject, topic, data) { |
|
5694 if (topic != this._permissionsPrompt && |
|
5695 topic != this._quotaPrompt && |
|
5696 topic != this._quotaCancel) { |
|
5697 throw new Error("Unexpected topic!"); |
|
5698 } |
|
5699 |
|
5700 var requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor); |
|
5701 |
|
5702 var contentWindow = requestor.getInterface(Ci.nsIDOMWindow); |
|
5703 var contentDocument = contentWindow.document; |
|
5704 var browserWindow = |
|
5705 OfflineApps._getBrowserWindowForContentWindow(contentWindow); |
|
5706 |
|
5707 if (browserWindow != window) { |
|
5708 // Must belong to some other window. |
|
5709 return; |
|
5710 } |
|
5711 |
|
5712 var browser = |
|
5713 OfflineApps._getBrowserForContentWindow(browserWindow, contentWindow); |
|
5714 |
|
5715 var host = contentDocument.documentURIObject.asciiHost; |
|
5716 |
|
5717 var message; |
|
5718 var responseTopic; |
|
5719 if (topic == this._permissionsPrompt) { |
|
5720 message = gNavigatorBundle.getFormattedString("offlineApps.available", |
|
5721 [ host ]); |
|
5722 responseTopic = this._permissionsResponse; |
|
5723 } |
|
5724 else if (topic == this._quotaPrompt) { |
|
5725 message = gNavigatorBundle.getFormattedString("indexedDB.usage", |
|
5726 [ host, data ]); |
|
5727 responseTopic = this._quotaResponse; |
|
5728 } |
|
5729 else if (topic == this._quotaCancel) { |
|
5730 responseTopic = this._quotaResponse; |
|
5731 } |
|
5732 |
|
5733 const hiddenTimeoutDuration = 30000; // 30 seconds |
|
5734 const firstTimeoutDuration = 300000; // 5 minutes |
|
5735 |
|
5736 var timeoutId; |
|
5737 |
|
5738 var observer = requestor.getInterface(Ci.nsIObserver); |
|
5739 |
|
5740 var mainAction = { |
|
5741 label: gNavigatorBundle.getString("offlineApps.allow"), |
|
5742 accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"), |
|
5743 callback: function() { |
|
5744 clearTimeout(timeoutId); |
|
5745 observer.observe(null, responseTopic, |
|
5746 Ci.nsIPermissionManager.ALLOW_ACTION); |
|
5747 } |
|
5748 }; |
|
5749 |
|
5750 var secondaryActions = [ |
|
5751 { |
|
5752 label: gNavigatorBundle.getString("offlineApps.never"), |
|
5753 accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"), |
|
5754 callback: function() { |
|
5755 clearTimeout(timeoutId); |
|
5756 observer.observe(null, responseTopic, |
|
5757 Ci.nsIPermissionManager.DENY_ACTION); |
|
5758 } |
|
5759 } |
|
5760 ]; |
|
5761 |
|
5762 // This will be set to the result of PopupNotifications.show() below, or to |
|
5763 // the result of PopupNotifications.getNotification() if this is a |
|
5764 // quotaCancel notification. |
|
5765 var notification; |
|
5766 |
|
5767 function timeoutNotification() { |
|
5768 // Remove the notification. |
|
5769 if (notification) { |
|
5770 notification.remove(); |
|
5771 } |
|
5772 |
|
5773 // Clear all of our timeout stuff. We may be called directly, not just |
|
5774 // when the timeout actually elapses. |
|
5775 clearTimeout(timeoutId); |
|
5776 |
|
5777 // And tell the page that the popup timed out. |
|
5778 observer.observe(null, responseTopic, |
|
5779 Ci.nsIPermissionManager.UNKNOWN_ACTION); |
|
5780 } |
|
5781 |
|
5782 var options = { |
|
5783 eventCallback: function(state) { |
|
5784 // Don't do anything if the timeout has not been set yet. |
|
5785 if (!timeoutId) { |
|
5786 return; |
|
5787 } |
|
5788 |
|
5789 // If the popup is being dismissed start the short timeout. |
|
5790 if (state == "dismissed") { |
|
5791 clearTimeout(timeoutId); |
|
5792 timeoutId = setTimeout(timeoutNotification, hiddenTimeoutDuration); |
|
5793 return; |
|
5794 } |
|
5795 |
|
5796 // If the popup is being re-shown then clear the timeout allowing |
|
5797 // unlimited waiting. |
|
5798 if (state == "shown") { |
|
5799 clearTimeout(timeoutId); |
|
5800 } |
|
5801 } |
|
5802 }; |
|
5803 |
|
5804 if (topic == this._quotaCancel) { |
|
5805 notification = PopupNotifications.getNotification(this._quotaPrompt, |
|
5806 browser); |
|
5807 timeoutNotification(); |
|
5808 return; |
|
5809 } |
|
5810 |
|
5811 notification = PopupNotifications.show(browser, topic, message, |
|
5812 this._notificationIcon, mainAction, |
|
5813 secondaryActions, options); |
|
5814 |
|
5815 // Set the timeoutId after the popup has been created, and use the long |
|
5816 // timeout value. If the user doesn't notice the popup after this amount of |
|
5817 // time then it is most likely not visible and we want to alert the page. |
|
5818 timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration); |
|
5819 } |
|
5820 }; |
|
5821 |
|
5822 var CanvasPermissionPromptHelper = { |
|
5823 _permissionsPrompt: "canvas-permissions-prompt", |
|
5824 _notificationIcon: "canvas-notification-icon", |
|
5825 |
|
5826 init: |
|
5827 function CanvasPermissionPromptHelper_init() { |
|
5828 if (document.styleSheets && (document.styleSheets.length > 0)) try { |
|
5829 let ruleText = "panel[popupid=canvas-permissions-prompt] description { white-space: pre-wrap; }"; |
|
5830 let sheet = document.styleSheets[0]; |
|
5831 sheet.insertRule(ruleText, sheet.cssRules.length); |
|
5832 } catch (e) {}; |
|
5833 |
|
5834 Services.obs.addObserver(this, this._permissionsPrompt, false); |
|
5835 }, |
|
5836 |
|
5837 uninit: |
|
5838 function CanvasPermissionPromptHelper_uninit() { |
|
5839 Services.obs.removeObserver(this, this._permissionsPrompt, false); |
|
5840 }, |
|
5841 |
|
5842 // aSubject is an nsIDOMWindow. |
|
5843 // aData is an URL string. |
|
5844 observe: |
|
5845 function CanvasPermissionPromptHelper_observe(aSubject, aTopic, aData) { |
|
5846 if ((aTopic != this._permissionsPrompt) || !aData) |
|
5847 throw new Error("Unexpected topic or missing URL"); |
|
5848 |
|
5849 var uri = makeURI(aData); |
|
5850 var contentWindow = aSubject.QueryInterface(Ci.nsIDOMWindow); |
|
5851 var contentDocument = contentWindow.document; |
|
5852 var browserWindow = |
|
5853 OfflineApps._getBrowserWindowForContentWindow(contentWindow); |
|
5854 |
|
5855 if (browserWindow != window) { |
|
5856 // Must belong to some other window. |
|
5857 return; |
|
5858 } |
|
5859 |
|
5860 // If canvas prompt is already displayed, just return. This is OK (and |
|
5861 // more efficient) since this permission is associated with the top |
|
5862 // browser's URL. |
|
5863 if (PopupNotifications.getNotification(aTopic, browser)) |
|
5864 return; |
|
5865 |
|
5866 var bundleSvc = Cc["@mozilla.org/intl/stringbundle;1"]. |
|
5867 getService(Ci.nsIStringBundleService); |
|
5868 var torBtnBundle; |
|
5869 try { |
|
5870 torBtnBundle = bundleSvc.createBundle( |
|
5871 "chrome://torbutton/locale/torbutton.properties"); |
|
5872 } catch (e) {} |
|
5873 |
|
5874 var message = getLocalizedString("canvas.siteprompt", [ uri.asciiHost ]); |
|
5875 |
|
5876 var mainAction = { |
|
5877 label: getLocalizedString("canvas.notNow"), |
|
5878 accessKey: getLocalizedString("canvas.notNowAccessKey"), |
|
5879 callback: function() { |
|
5880 return null; |
|
5881 } |
|
5882 }; |
|
5883 |
|
5884 var secondaryActions = [ |
|
5885 { |
|
5886 label: getLocalizedString("canvas.never"), |
|
5887 accessKey: getLocalizedString("canvas.neverAccessKey"), |
|
5888 callback: function() { |
|
5889 setCanvasPermission(uri, Ci.nsIPermissionManager.DENY_ACTION); |
|
5890 } |
|
5891 }, |
|
5892 { |
|
5893 label: getLocalizedString("canvas.allow"), |
|
5894 accessKey: getLocalizedString("canvas.allowAccessKey"), |
|
5895 callback: function() { |
|
5896 setCanvasPermission(uri, Ci.nsIPermissionManager.ALLOW_ACTION); |
|
5897 } |
|
5898 } |
|
5899 ]; |
|
5900 |
|
5901 // Since we have a process in place to perform localization for the |
|
5902 // Torbutton extension, get our strings from the extension if possible. |
|
5903 function getLocalizedString(aID, aParams) { |
|
5904 var s; |
|
5905 if (torBtnBundle) try { |
|
5906 if (aParams) |
|
5907 s = torBtnBundle.formatStringFromName(aID, aParams, aParams.length); |
|
5908 else |
|
5909 s = torBtnBundle.GetStringFromName(aID); |
|
5910 } catch (e) {} |
|
5911 |
|
5912 if (!s) { |
|
5913 if (aParams) |
|
5914 s = gNavigatorBundle.getFormattedString(aID, aParams); |
|
5915 else |
|
5916 s = gNavigatorBundle.getString(aID); |
|
5917 } |
|
5918 |
|
5919 return s; |
|
5920 } |
|
5921 |
|
5922 function setCanvasPermission(aURI, aPerm) { |
|
5923 Services.perms.add(aURI, "canvas/extractData", aPerm, |
|
5924 Ci.nsIPermissionManager.EXPIRE_NEVER); |
|
5925 } |
|
5926 |
|
5927 var browser = OfflineApps._getBrowserForContentWindow(browserWindow, |
|
5928 contentWindow); |
|
5929 notification = PopupNotifications.show(browser, aTopic, message, |
|
5930 this._notificationIcon, mainAction, |
|
5931 secondaryActions, null); |
|
5932 } |
|
5933 }; |
|
5934 |
|
5935 function WindowIsClosing() |
|
5936 { |
|
5937 if (TabView.isVisible()) { |
|
5938 TabView.hide(); |
|
5939 return false; |
|
5940 } |
|
5941 |
|
5942 if (!closeWindow(false, warnAboutClosingWindow)) |
|
5943 return false; |
|
5944 |
|
5945 // Bug 967873 - Proxy nsDocumentViewer::PermitUnload to the child process |
|
5946 if (gMultiProcessBrowser) |
|
5947 return true; |
|
5948 |
|
5949 for (let browser of gBrowser.browsers) { |
|
5950 let ds = browser.docShell; |
|
5951 if (ds.contentViewer && !ds.contentViewer.permitUnload()) |
|
5952 return false; |
|
5953 } |
|
5954 |
|
5955 return true; |
|
5956 } |
|
5957 |
|
5958 /** |
|
5959 * Checks if this is the last full *browser* window around. If it is, this will |
|
5960 * be communicated like quitting. Otherwise, we warn about closing multiple tabs. |
|
5961 * @returns true if closing can proceed, false if it got cancelled. |
|
5962 */ |
|
5963 function warnAboutClosingWindow() { |
|
5964 // Popups aren't considered full browser windows; we also ignore private windows. |
|
5965 let isPBWindow = PrivateBrowsingUtils.isWindowPrivate(window) && |
|
5966 !PrivateBrowsingUtils.permanentPrivateBrowsing; |
|
5967 if (!isPBWindow && !toolbar.visible) |
|
5968 return gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL); |
|
5969 |
|
5970 // Figure out if there's at least one other browser window around. |
|
5971 let e = Services.wm.getEnumerator("navigator:browser"); |
|
5972 let otherPBWindowExists = false; |
|
5973 let nonPopupPresent = false; |
|
5974 while (e.hasMoreElements()) { |
|
5975 let win = e.getNext(); |
|
5976 if (!win.closed && win != window) { |
|
5977 if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win)) |
|
5978 otherPBWindowExists = true; |
|
5979 if (win.toolbar.visible) |
|
5980 nonPopupPresent = true; |
|
5981 // If the current window is not in private browsing mode we don't need to |
|
5982 // look for other pb windows, we can leave the loop when finding the |
|
5983 // first non-popup window. If however the current window is in private |
|
5984 // browsing mode then we need at least one other pb and one non-popup |
|
5985 // window to break out early. |
|
5986 if ((!isPBWindow || otherPBWindowExists) && nonPopupPresent) |
|
5987 break; |
|
5988 } |
|
5989 } |
|
5990 |
|
5991 if (isPBWindow && !otherPBWindowExists) { |
|
5992 let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"]. |
|
5993 createInstance(Ci.nsISupportsPRBool); |
|
5994 exitingCanceled.data = false; |
|
5995 Services.obs.notifyObservers(exitingCanceled, |
|
5996 "last-pb-context-exiting", |
|
5997 null); |
|
5998 if (exitingCanceled.data) |
|
5999 return false; |
|
6000 } |
|
6001 |
|
6002 if (nonPopupPresent) { |
|
6003 return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL); |
|
6004 } |
|
6005 |
|
6006 let os = Services.obs; |
|
6007 |
|
6008 let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"]. |
|
6009 createInstance(Ci.nsISupportsPRBool); |
|
6010 os.notifyObservers(closingCanceled, |
|
6011 "browser-lastwindow-close-requested", null); |
|
6012 if (closingCanceled.data) |
|
6013 return false; |
|
6014 |
|
6015 os.notifyObservers(null, "browser-lastwindow-close-granted", null); |
|
6016 |
|
6017 #ifdef XP_MACOSX |
|
6018 // OS X doesn't quit the application when the last window is closed, but keeps |
|
6019 // the session alive. Hence don't prompt users to save tabs, but warn about |
|
6020 // closing multiple tabs. |
|
6021 return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL); |
|
6022 #else |
|
6023 return true; |
|
6024 #endif |
|
6025 } |
|
6026 |
|
6027 var MailIntegration = { |
|
6028 sendLinkForWindow: function (aWindow) { |
|
6029 this.sendMessage(aWindow.location.href, |
|
6030 aWindow.document.title); |
|
6031 }, |
|
6032 |
|
6033 sendMessage: function (aBody, aSubject) { |
|
6034 // generate a mailto url based on the url and the url's title |
|
6035 var mailtoUrl = "mailto:"; |
|
6036 if (aBody) { |
|
6037 mailtoUrl += "?body=" + encodeURIComponent(aBody); |
|
6038 mailtoUrl += "&subject=" + encodeURIComponent(aSubject); |
|
6039 } |
|
6040 |
|
6041 var uri = makeURI(mailtoUrl); |
|
6042 |
|
6043 // now pass this uri to the operating system |
|
6044 this._launchExternalUrl(uri); |
|
6045 }, |
|
6046 |
|
6047 // a generic method which can be used to pass arbitrary urls to the operating |
|
6048 // system. |
|
6049 // aURL --> a nsIURI which represents the url to launch |
|
6050 _launchExternalUrl: function (aURL) { |
|
6051 var extProtocolSvc = |
|
6052 Cc["@mozilla.org/uriloader/external-protocol-service;1"] |
|
6053 .getService(Ci.nsIExternalProtocolService); |
|
6054 if (extProtocolSvc) |
|
6055 extProtocolSvc.loadUrl(aURL); |
|
6056 } |
|
6057 }; |
|
6058 |
|
6059 function BrowserOpenAddonsMgr(aView) { |
|
6060 if (aView) { |
|
6061 let emWindow; |
|
6062 let browserWindow; |
|
6063 |
|
6064 var receivePong = function receivePong(aSubject, aTopic, aData) { |
|
6065 let browserWin = aSubject.QueryInterface(Ci.nsIInterfaceRequestor) |
|
6066 .getInterface(Ci.nsIWebNavigation) |
|
6067 .QueryInterface(Ci.nsIDocShellTreeItem) |
|
6068 .rootTreeItem |
|
6069 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
6070 .getInterface(Ci.nsIDOMWindow); |
|
6071 if (!emWindow || browserWin == window /* favor the current window */) { |
|
6072 emWindow = aSubject; |
|
6073 browserWindow = browserWin; |
|
6074 } |
|
6075 } |
|
6076 Services.obs.addObserver(receivePong, "EM-pong", false); |
|
6077 Services.obs.notifyObservers(null, "EM-ping", ""); |
|
6078 Services.obs.removeObserver(receivePong, "EM-pong"); |
|
6079 |
|
6080 if (emWindow) { |
|
6081 emWindow.loadView(aView); |
|
6082 browserWindow.gBrowser.selectedTab = |
|
6083 browserWindow.gBrowser._getTabForContentWindow(emWindow); |
|
6084 emWindow.focus(); |
|
6085 return; |
|
6086 } |
|
6087 } |
|
6088 |
|
6089 var newLoad = !switchToTabHavingURI("about:addons", true); |
|
6090 |
|
6091 if (aView) { |
|
6092 // This must be a new load, else the ping/pong would have |
|
6093 // found the window above. |
|
6094 Services.obs.addObserver(function observer(aSubject, aTopic, aData) { |
|
6095 Services.obs.removeObserver(observer, aTopic); |
|
6096 aSubject.loadView(aView); |
|
6097 }, "EM-loaded", false); |
|
6098 } |
|
6099 } |
|
6100 |
|
6101 function GetSearchFieldBookmarkData(node) { |
|
6102 var charset = node.ownerDocument.characterSet; |
|
6103 |
|
6104 var formBaseURI = makeURI(node.form.baseURI, |
|
6105 charset); |
|
6106 |
|
6107 var formURI = makeURI(node.form.getAttribute("action"), |
|
6108 charset, |
|
6109 formBaseURI); |
|
6110 |
|
6111 var spec = formURI.spec; |
|
6112 |
|
6113 var isURLEncoded = |
|
6114 (node.form.method.toUpperCase() == "POST" |
|
6115 && (node.form.enctype == "application/x-www-form-urlencoded" || |
|
6116 node.form.enctype == "")); |
|
6117 |
|
6118 var title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill", |
|
6119 [node.ownerDocument.title]); |
|
6120 var description = PlacesUIUtils.getDescriptionFromDocument(node.ownerDocument); |
|
6121 |
|
6122 var formData = []; |
|
6123 |
|
6124 function escapeNameValuePair(aName, aValue, aIsFormUrlEncoded) { |
|
6125 if (aIsFormUrlEncoded) |
|
6126 return escape(aName + "=" + aValue); |
|
6127 else |
|
6128 return escape(aName) + "=" + escape(aValue); |
|
6129 } |
|
6130 |
|
6131 for (let el of node.form.elements) { |
|
6132 if (!el.type) // happens with fieldsets |
|
6133 continue; |
|
6134 |
|
6135 if (el == node) { |
|
6136 formData.push((isURLEncoded) ? escapeNameValuePair(el.name, "%s", true) : |
|
6137 // Don't escape "%s", just append |
|
6138 escapeNameValuePair(el.name, "", false) + "%s"); |
|
6139 continue; |
|
6140 } |
|
6141 |
|
6142 let type = el.type.toLowerCase(); |
|
6143 |
|
6144 if (((el instanceof HTMLInputElement && el.mozIsTextField(true)) || |
|
6145 type == "hidden" || type == "textarea") || |
|
6146 ((type == "checkbox" || type == "radio") && el.checked)) { |
|
6147 formData.push(escapeNameValuePair(el.name, el.value, isURLEncoded)); |
|
6148 } else if (el instanceof HTMLSelectElement && el.selectedIndex >= 0) { |
|
6149 for (var j=0; j < el.options.length; j++) { |
|
6150 if (el.options[j].selected) |
|
6151 formData.push(escapeNameValuePair(el.name, el.options[j].value, |
|
6152 isURLEncoded)); |
|
6153 } |
|
6154 } |
|
6155 } |
|
6156 |
|
6157 var postData; |
|
6158 |
|
6159 if (isURLEncoded) |
|
6160 postData = formData.join("&"); |
|
6161 else |
|
6162 spec += "?" + formData.join("&"); |
|
6163 |
|
6164 return { |
|
6165 spec: spec, |
|
6166 title: title, |
|
6167 description: description, |
|
6168 postData: postData, |
|
6169 charSet: charset |
|
6170 }; |
|
6171 } |
|
6172 |
|
6173 |
|
6174 function AddKeywordForSearchField() { |
|
6175 bookmarkData = GetSearchFieldBookmarkData(document.popupNode); |
|
6176 |
|
6177 PlacesUIUtils.showBookmarkDialog({ action: "add" |
|
6178 , type: "bookmark" |
|
6179 , uri: makeURI(bookmarkData.spec) |
|
6180 , title: bookmarkData.title |
|
6181 , description: bookmarkData.description |
|
6182 , keyword: "" |
|
6183 , postData: bookmarkData.postData |
|
6184 , charSet: bookmarkData.charset |
|
6185 , hiddenRows: [ "location" |
|
6186 , "description" |
|
6187 , "tags" |
|
6188 , "loadInSidebar" ] |
|
6189 }, window); |
|
6190 } |
|
6191 |
|
6192 function SwitchDocumentDirection(aWindow) { |
|
6193 // document.dir can also be "auto", in which case it won't change |
|
6194 if (aWindow.document.dir == "ltr" || aWindow.document.dir == "") { |
|
6195 aWindow.document.dir = "rtl"; |
|
6196 } else if (aWindow.document.dir == "rtl") { |
|
6197 aWindow.document.dir = "ltr"; |
|
6198 } |
|
6199 for (var run = 0; run < aWindow.frames.length; run++) |
|
6200 SwitchDocumentDirection(aWindow.frames[run]); |
|
6201 } |
|
6202 |
|
6203 function convertFromUnicode(charset, str) |
|
6204 { |
|
6205 try { |
|
6206 var unicodeConverter = Components |
|
6207 .classes["@mozilla.org/intl/scriptableunicodeconverter"] |
|
6208 .createInstance(Components.interfaces.nsIScriptableUnicodeConverter); |
|
6209 unicodeConverter.charset = charset; |
|
6210 str = unicodeConverter.ConvertFromUnicode(str); |
|
6211 return str + unicodeConverter.Finish(); |
|
6212 } catch(ex) { |
|
6213 return null; |
|
6214 } |
|
6215 } |
|
6216 |
|
6217 /** |
|
6218 * Re-open a closed tab. |
|
6219 * @param aIndex |
|
6220 * The index of the tab (via SessionStore.getClosedTabData) |
|
6221 * @returns a reference to the reopened tab. |
|
6222 */ |
|
6223 function undoCloseTab(aIndex) { |
|
6224 // wallpaper patch to prevent an unnecessary blank tab (bug 343895) |
|
6225 var blankTabToRemove = null; |
|
6226 if (gBrowser.tabs.length == 1 && isTabEmpty(gBrowser.selectedTab)) |
|
6227 blankTabToRemove = gBrowser.selectedTab; |
|
6228 |
|
6229 var tab = null; |
|
6230 if (SessionStore.getClosedTabCount(window) > (aIndex || 0)) { |
|
6231 TabView.prepareUndoCloseTab(blankTabToRemove); |
|
6232 tab = SessionStore.undoCloseTab(window, aIndex || 0); |
|
6233 TabView.afterUndoCloseTab(); |
|
6234 |
|
6235 if (blankTabToRemove) |
|
6236 gBrowser.removeTab(blankTabToRemove); |
|
6237 } |
|
6238 |
|
6239 return tab; |
|
6240 } |
|
6241 |
|
6242 /** |
|
6243 * Re-open a closed window. |
|
6244 * @param aIndex |
|
6245 * The index of the window (via SessionStore.getClosedWindowData) |
|
6246 * @returns a reference to the reopened window. |
|
6247 */ |
|
6248 function undoCloseWindow(aIndex) { |
|
6249 let window = null; |
|
6250 if (SessionStore.getClosedWindowCount() > (aIndex || 0)) |
|
6251 window = SessionStore.undoCloseWindow(aIndex || 0); |
|
6252 |
|
6253 return window; |
|
6254 } |
|
6255 |
|
6256 /* |
|
6257 * Determines if a tab is "empty", usually used in the context of determining |
|
6258 * if it's ok to close the tab. |
|
6259 */ |
|
6260 function isTabEmpty(aTab) { |
|
6261 if (aTab.hasAttribute("busy")) |
|
6262 return false; |
|
6263 |
|
6264 let browser = aTab.linkedBrowser; |
|
6265 if (!isBlankPageURL(browser.currentURI.spec)) |
|
6266 return false; |
|
6267 |
|
6268 // Bug 863515 - Make content.opener checks work in electrolysis. |
|
6269 if (!gMultiProcessBrowser && browser.contentWindow.opener) |
|
6270 return false; |
|
6271 |
|
6272 if (browser.sessionHistory && browser.sessionHistory.count >= 2) |
|
6273 return false; |
|
6274 |
|
6275 return true; |
|
6276 } |
|
6277 |
|
6278 #ifdef MOZ_SERVICES_SYNC |
|
6279 function BrowserOpenSyncTabs() { |
|
6280 switchToTabHavingURI("about:sync-tabs", true); |
|
6281 } |
|
6282 #endif |
|
6283 |
|
6284 /** |
|
6285 * Format a URL |
|
6286 * eg: |
|
6287 * echo formatURL("https://addons.mozilla.org/%LOCALE%/%APP%/%VERSION%/"); |
|
6288 * > https://addons.mozilla.org/en-US/firefox/3.0a1/ |
|
6289 * |
|
6290 * Currently supported built-ins are LOCALE, APP, and any value from nsIXULAppInfo, uppercased. |
|
6291 */ |
|
6292 function formatURL(aFormat, aIsPref) { |
|
6293 var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); |
|
6294 return aIsPref ? formatter.formatURLPref(aFormat) : formatter.formatURL(aFormat); |
|
6295 } |
|
6296 |
|
6297 /** |
|
6298 * Utility object to handle manipulations of the identity indicators in the UI |
|
6299 */ |
|
6300 var gIdentityHandler = { |
|
6301 // Mode strings used to control CSS display |
|
6302 IDENTITY_MODE_IDENTIFIED : "verifiedIdentity", // High-quality identity information |
|
6303 IDENTITY_MODE_DOMAIN_VERIFIED : "verifiedDomain", // Minimal SSL CA-signed domain verification |
|
6304 IDENTITY_MODE_UNKNOWN : "unknownIdentity", // No trusted identity information |
|
6305 IDENTITY_MODE_MIXED_DISPLAY_LOADED : "unknownIdentity mixedContent mixedDisplayContent", // SSL with unauthenticated display content |
|
6306 IDENTITY_MODE_MIXED_ACTIVE_LOADED : "unknownIdentity mixedContent mixedActiveContent", // SSL with unauthenticated active (and perhaps also display) content |
|
6307 IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED : "unknownIdentity mixedContent mixedDisplayContentLoadedActiveBlocked", // SSL with unauthenticated display content; unauthenticated active content is blocked. |
|
6308 IDENTITY_MODE_CHROMEUI : "chromeUI", // Part of the product's UI |
|
6309 |
|
6310 // Cache the most recent SSLStatus and Location seen in checkIdentity |
|
6311 _lastStatus : null, |
|
6312 _lastUri : null, |
|
6313 _mode : "unknownIdentity", |
|
6314 |
|
6315 // smart getters |
|
6316 get _encryptionLabel () { |
|
6317 delete this._encryptionLabel; |
|
6318 this._encryptionLabel = {}; |
|
6319 this._encryptionLabel[this.IDENTITY_MODE_DOMAIN_VERIFIED] = |
|
6320 gNavigatorBundle.getString("identity.encrypted2"); |
|
6321 this._encryptionLabel[this.IDENTITY_MODE_IDENTIFIED] = |
|
6322 gNavigatorBundle.getString("identity.encrypted2"); |
|
6323 this._encryptionLabel[this.IDENTITY_MODE_UNKNOWN] = |
|
6324 gNavigatorBundle.getString("identity.unencrypted"); |
|
6325 this._encryptionLabel[this.IDENTITY_MODE_MIXED_DISPLAY_LOADED] = |
|
6326 gNavigatorBundle.getString("identity.mixed_display_loaded"); |
|
6327 this._encryptionLabel[this.IDENTITY_MODE_MIXED_ACTIVE_LOADED] = |
|
6328 gNavigatorBundle.getString("identity.mixed_active_loaded2"); |
|
6329 this._encryptionLabel[this.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED] = |
|
6330 gNavigatorBundle.getString("identity.mixed_display_loaded"); |
|
6331 return this._encryptionLabel; |
|
6332 }, |
|
6333 get _identityPopup () { |
|
6334 delete this._identityPopup; |
|
6335 return this._identityPopup = document.getElementById("identity-popup"); |
|
6336 }, |
|
6337 get _identityBox () { |
|
6338 delete this._identityBox; |
|
6339 return this._identityBox = document.getElementById("identity-box"); |
|
6340 }, |
|
6341 get _identityPopupContentBox () { |
|
6342 delete this._identityPopupContentBox; |
|
6343 return this._identityPopupContentBox = |
|
6344 document.getElementById("identity-popup-content-box"); |
|
6345 }, |
|
6346 get _identityPopupChromeLabel () { |
|
6347 delete this._identityPopupChromeLabel; |
|
6348 return this._identityPopupChromeLabel = |
|
6349 document.getElementById("identity-popup-chromeLabel"); |
|
6350 }, |
|
6351 get _identityPopupContentHost () { |
|
6352 delete this._identityPopupContentHost; |
|
6353 return this._identityPopupContentHost = |
|
6354 document.getElementById("identity-popup-content-host"); |
|
6355 }, |
|
6356 get _identityPopupContentOwner () { |
|
6357 delete this._identityPopupContentOwner; |
|
6358 return this._identityPopupContentOwner = |
|
6359 document.getElementById("identity-popup-content-owner"); |
|
6360 }, |
|
6361 get _identityPopupContentSupp () { |
|
6362 delete this._identityPopupContentSupp; |
|
6363 return this._identityPopupContentSupp = |
|
6364 document.getElementById("identity-popup-content-supplemental"); |
|
6365 }, |
|
6366 get _identityPopupContentVerif () { |
|
6367 delete this._identityPopupContentVerif; |
|
6368 return this._identityPopupContentVerif = |
|
6369 document.getElementById("identity-popup-content-verifier"); |
|
6370 }, |
|
6371 get _identityPopupEncLabel () { |
|
6372 delete this._identityPopupEncLabel; |
|
6373 return this._identityPopupEncLabel = |
|
6374 document.getElementById("identity-popup-encryption-label"); |
|
6375 }, |
|
6376 get _identityIconLabel () { |
|
6377 delete this._identityIconLabel; |
|
6378 return this._identityIconLabel = document.getElementById("identity-icon-label"); |
|
6379 }, |
|
6380 get _overrideService () { |
|
6381 delete this._overrideService; |
|
6382 return this._overrideService = Cc["@mozilla.org/security/certoverride;1"] |
|
6383 .getService(Ci.nsICertOverrideService); |
|
6384 }, |
|
6385 get _identityIconCountryLabel () { |
|
6386 delete this._identityIconCountryLabel; |
|
6387 return this._identityIconCountryLabel = document.getElementById("identity-icon-country-label"); |
|
6388 }, |
|
6389 get _identityIcon () { |
|
6390 delete this._identityIcon; |
|
6391 return this._identityIcon = document.getElementById("page-proxy-favicon"); |
|
6392 }, |
|
6393 get _permissionsContainer () { |
|
6394 delete this._permissionsContainer; |
|
6395 return this._permissionsContainer = document.getElementById("identity-popup-permissions"); |
|
6396 }, |
|
6397 get _permissionList () { |
|
6398 delete this._permissionList; |
|
6399 return this._permissionList = document.getElementById("identity-popup-permission-list"); |
|
6400 }, |
|
6401 |
|
6402 /** |
|
6403 * Rebuild cache of the elements that may or may not exist depending |
|
6404 * on whether there's a location bar. |
|
6405 */ |
|
6406 _cacheElements : function() { |
|
6407 delete this._identityBox; |
|
6408 delete this._identityIconLabel; |
|
6409 delete this._identityIconCountryLabel; |
|
6410 delete this._identityIcon; |
|
6411 delete this._permissionsContainer; |
|
6412 delete this._permissionList; |
|
6413 this._identityBox = document.getElementById("identity-box"); |
|
6414 this._identityIconLabel = document.getElementById("identity-icon-label"); |
|
6415 this._identityIconCountryLabel = document.getElementById("identity-icon-country-label"); |
|
6416 this._identityIcon = document.getElementById("page-proxy-favicon"); |
|
6417 this._permissionsContainer = document.getElementById("identity-popup-permissions"); |
|
6418 this._permissionList = document.getElementById("identity-popup-permission-list"); |
|
6419 }, |
|
6420 |
|
6421 /** |
|
6422 * Handler for commands on the help button in the "identity-popup" panel. |
|
6423 */ |
|
6424 handleHelpCommand : function(event) { |
|
6425 openHelpLink("secure-connection"); |
|
6426 this._identityPopup.hidePopup(); |
|
6427 }, |
|
6428 |
|
6429 /** |
|
6430 * Handler for mouseclicks on the "More Information" button in the |
|
6431 * "identity-popup" panel. |
|
6432 */ |
|
6433 handleMoreInfoClick : function(event) { |
|
6434 displaySecurityInfo(); |
|
6435 event.stopPropagation(); |
|
6436 this._identityPopup.hidePopup(); |
|
6437 }, |
|
6438 |
|
6439 /** |
|
6440 * Helper to parse out the important parts of _lastStatus (of the SSL cert in |
|
6441 * particular) for use in constructing identity UI strings |
|
6442 */ |
|
6443 getIdentityData : function() { |
|
6444 var result = {}; |
|
6445 var status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus); |
|
6446 var cert = status.serverCert; |
|
6447 |
|
6448 // Human readable name of Subject |
|
6449 result.subjectOrg = cert.organization; |
|
6450 |
|
6451 // SubjectName fields, broken up for individual access |
|
6452 if (cert.subjectName) { |
|
6453 result.subjectNameFields = {}; |
|
6454 cert.subjectName.split(",").forEach(function(v) { |
|
6455 var field = v.split("="); |
|
6456 this[field[0]] = field[1]; |
|
6457 }, result.subjectNameFields); |
|
6458 |
|
6459 // Call out city, state, and country specifically |
|
6460 result.city = result.subjectNameFields.L; |
|
6461 result.state = result.subjectNameFields.ST; |
|
6462 result.country = result.subjectNameFields.C; |
|
6463 } |
|
6464 |
|
6465 // Human readable name of Certificate Authority |
|
6466 result.caOrg = cert.issuerOrganization || cert.issuerCommonName; |
|
6467 result.cert = cert; |
|
6468 |
|
6469 return result; |
|
6470 }, |
|
6471 |
|
6472 /** |
|
6473 * Determine the identity of the page being displayed by examining its SSL cert |
|
6474 * (if available) and, if necessary, update the UI to reflect this. Intended to |
|
6475 * be called by onSecurityChange |
|
6476 * |
|
6477 * @param PRUint32 state |
|
6478 * @param nsIURI uri The address for which the UI should be updated. |
|
6479 */ |
|
6480 checkIdentity : function(state, uri) { |
|
6481 var currentStatus = gBrowser.securityUI |
|
6482 .QueryInterface(Components.interfaces.nsISSLStatusProvider) |
|
6483 .SSLStatus; |
|
6484 this._lastStatus = currentStatus; |
|
6485 this._lastUri = uri; |
|
6486 |
|
6487 let nsIWebProgressListener = Ci.nsIWebProgressListener; |
|
6488 |
|
6489 // For some URIs like data: we can't get a host and so can't do |
|
6490 // anything useful here. |
|
6491 let unknown = false; |
|
6492 try { |
|
6493 uri.host; |
|
6494 } catch (e) { unknown = true; } |
|
6495 |
|
6496 // Chrome URIs however get special treatment. Some chrome URIs are |
|
6497 // whitelisted to provide a positive security signal to the user. |
|
6498 let whitelist = /^about:(accounts|addons|app-manager|config|crashes|customizing|healthreport|home|newaddon|permissions|preferences|privatebrowsing|sessionrestore|support|welcomeback)/i; |
|
6499 let isChromeUI = uri.schemeIs("about") && whitelist.test(uri.spec); |
|
6500 if (isChromeUI) { |
|
6501 this.setMode(this.IDENTITY_MODE_CHROMEUI); |
|
6502 } else if (unknown) { |
|
6503 this.setMode(this.IDENTITY_MODE_UNKNOWN); |
|
6504 } else if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) { |
|
6505 this.setMode(this.IDENTITY_MODE_IDENTIFIED); |
|
6506 } else if (state & nsIWebProgressListener.STATE_IS_SECURE) { |
|
6507 this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED); |
|
6508 } else if (state & nsIWebProgressListener.STATE_IS_BROKEN) { |
|
6509 if ((state & nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) && |
|
6510 gPrefService.getBoolPref("security.mixed_content.block_active_content")) { |
|
6511 this.setMode(this.IDENTITY_MODE_MIXED_ACTIVE_LOADED); |
|
6512 } else if ((state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) && |
|
6513 gPrefService.getBoolPref("security.mixed_content.block_active_content")) { |
|
6514 this.setMode(this.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED); |
|
6515 } else { |
|
6516 this.setMode(this.IDENTITY_MODE_MIXED_DISPLAY_LOADED); |
|
6517 } |
|
6518 } else { |
|
6519 this.setMode(this.IDENTITY_MODE_UNKNOWN); |
|
6520 } |
|
6521 |
|
6522 // Ensure the doorhanger is shown when mixed active content is blocked. |
|
6523 if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) |
|
6524 this.showMixedContentDoorhanger(); |
|
6525 }, |
|
6526 |
|
6527 /** |
|
6528 * Display the Mixed Content Blocker doohanger, providing an option |
|
6529 * to the user to override mixed content blocking |
|
6530 */ |
|
6531 showMixedContentDoorhanger : function() { |
|
6532 // If we've already got an active notification, bail out to avoid showing it repeatedly. |
|
6533 if (PopupNotifications.getNotification("mixed-content-blocked", gBrowser.selectedBrowser)) |
|
6534 return; |
|
6535 |
|
6536 let brandBundle = document.getElementById("bundle_brand"); |
|
6537 let brandShortName = brandBundle.getString("brandShortName"); |
|
6538 let messageString = gNavigatorBundle.getFormattedString("mixedContentBlocked.message", [brandShortName]); |
|
6539 let action = { |
|
6540 label: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.label"), |
|
6541 accessKey: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.accesskey"), |
|
6542 callback: function() { /* NOP */ } |
|
6543 }; |
|
6544 let secondaryActions = [ |
|
6545 { |
|
6546 label: gNavigatorBundle.getString("mixedContentBlocked.unblock.label"), |
|
6547 accessKey: gNavigatorBundle.getString("mixedContentBlocked.unblock.accesskey"), |
|
6548 callback: function() { |
|
6549 // Use telemetry to measure how often unblocking happens |
|
6550 const kMIXED_CONTENT_UNBLOCK_EVENT = 2; |
|
6551 let histogram = |
|
6552 Services.telemetry.getHistogramById("MIXED_CONTENT_UNBLOCK_COUNTER"); |
|
6553 histogram.add(kMIXED_CONTENT_UNBLOCK_EVENT); |
|
6554 // Reload the page with the content unblocked |
|
6555 BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT); |
|
6556 } |
|
6557 } |
|
6558 ]; |
|
6559 let options = { |
|
6560 dismissed: true, |
|
6561 learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "mixed-content", |
|
6562 }; |
|
6563 PopupNotifications.show(gBrowser.selectedBrowser, "mixed-content-blocked", |
|
6564 messageString, "mixed-content-blocked-notification-icon", |
|
6565 action, secondaryActions, options); |
|
6566 }, |
|
6567 |
|
6568 /** |
|
6569 * Return the eTLD+1 version of the current hostname |
|
6570 */ |
|
6571 getEffectiveHost : function() { |
|
6572 if (!this._IDNService) |
|
6573 this._IDNService = Cc["@mozilla.org/network/idn-service;1"] |
|
6574 .getService(Ci.nsIIDNService); |
|
6575 try { |
|
6576 let baseDomain = |
|
6577 Services.eTLD.getBaseDomainFromHost(this._lastUri.host); |
|
6578 return this._IDNService.convertToDisplayIDN(baseDomain, {}); |
|
6579 } catch (e) { |
|
6580 // If something goes wrong (e.g. host is an IP address) just fail back |
|
6581 // to the full domain. |
|
6582 return this._lastUri.host; |
|
6583 } |
|
6584 }, |
|
6585 |
|
6586 /** |
|
6587 * Update the UI to reflect the specified mode, which should be one of the |
|
6588 * IDENTITY_MODE_* constants. |
|
6589 */ |
|
6590 setMode : function(newMode) { |
|
6591 if (!this._identityBox) { |
|
6592 // No identity box means the identity box is not visible, in which |
|
6593 // case there's nothing to do. |
|
6594 return; |
|
6595 } |
|
6596 |
|
6597 this._identityPopup.className = newMode; |
|
6598 this._identityBox.className = newMode; |
|
6599 this.setIdentityMessages(newMode); |
|
6600 |
|
6601 // Update the popup too, if it's open |
|
6602 if (this._identityPopup.state == "open") |
|
6603 this.setPopupMessages(newMode); |
|
6604 |
|
6605 this._mode = newMode; |
|
6606 }, |
|
6607 |
|
6608 /** |
|
6609 * Set up the messages for the primary identity UI based on the specified mode, |
|
6610 * and the details of the SSL cert, where applicable |
|
6611 * |
|
6612 * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants. |
|
6613 */ |
|
6614 setIdentityMessages : function(newMode) { |
|
6615 let icon_label = ""; |
|
6616 let tooltip = ""; |
|
6617 let icon_country_label = ""; |
|
6618 let icon_labels_dir = "ltr"; |
|
6619 |
|
6620 switch (newMode) { |
|
6621 case this.IDENTITY_MODE_DOMAIN_VERIFIED: { |
|
6622 let iData = this.getIdentityData(); |
|
6623 |
|
6624 // Verifier is either the CA Org, for a normal cert, or a special string |
|
6625 // for certs that are trusted because of a security exception. |
|
6626 tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier", |
|
6627 [iData.caOrg]); |
|
6628 |
|
6629 // This can't throw, because URI's with a host that throw don't end up in this case. |
|
6630 let host = this._lastUri.host; |
|
6631 let port = 443; |
|
6632 try { |
|
6633 if (this._lastUri.port > 0) |
|
6634 port = this._lastUri.port; |
|
6635 } catch (e) {} |
|
6636 |
|
6637 if (this._overrideService.hasMatchingOverride(host, port, iData.cert, {}, {})) |
|
6638 tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you"); |
|
6639 |
|
6640 break; } |
|
6641 case this.IDENTITY_MODE_IDENTIFIED: { |
|
6642 // If it's identified, then we can populate the dialog with credentials |
|
6643 let iData = this.getIdentityData(); |
|
6644 tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier", |
|
6645 [iData.caOrg]); |
|
6646 icon_label = iData.subjectOrg; |
|
6647 if (iData.country) |
|
6648 icon_country_label = "(" + iData.country + ")"; |
|
6649 |
|
6650 // If the organization name starts with an RTL character, then |
|
6651 // swap the positions of the organization and country code labels. |
|
6652 // The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI |
|
6653 // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets |
|
6654 // fixed, this test should be replaced by one adhering to the |
|
6655 // Unicode Bidirectional Algorithm proper (at the paragraph level). |
|
6656 icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ? |
|
6657 "rtl" : "ltr"; |
|
6658 break; } |
|
6659 case this.IDENTITY_MODE_CHROMEUI: |
|
6660 let brandBundle = document.getElementById("bundle_brand"); |
|
6661 icon_label = brandBundle.getString("brandShortName"); |
|
6662 break; |
|
6663 default: |
|
6664 tooltip = gNavigatorBundle.getString("identity.unknown.tooltip"); |
|
6665 } |
|
6666 |
|
6667 // Push the appropriate strings out to the UI |
|
6668 this._identityBox.tooltipText = tooltip; |
|
6669 this._identityIconLabel.value = icon_label; |
|
6670 this._identityIconCountryLabel.value = icon_country_label; |
|
6671 // Set cropping and direction |
|
6672 this._identityIconLabel.crop = icon_country_label ? "end" : "center"; |
|
6673 this._identityIconLabel.parentNode.style.direction = icon_labels_dir; |
|
6674 // Hide completely if the organization label is empty |
|
6675 this._identityIconLabel.parentNode.collapsed = icon_label ? false : true; |
|
6676 }, |
|
6677 |
|
6678 /** |
|
6679 * Set up the title and content messages for the identity message popup, |
|
6680 * based on the specified mode, and the details of the SSL cert, where |
|
6681 * applicable |
|
6682 * |
|
6683 * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants. |
|
6684 */ |
|
6685 setPopupMessages : function(newMode) { |
|
6686 |
|
6687 this._identityPopup.className = newMode; |
|
6688 this._identityPopupContentBox.className = newMode; |
|
6689 |
|
6690 // Set the static strings up front |
|
6691 this._identityPopupEncLabel.textContent = this._encryptionLabel[newMode]; |
|
6692 |
|
6693 // Initialize the optional strings to empty values |
|
6694 let supplemental = ""; |
|
6695 let verifier = ""; |
|
6696 let host = ""; |
|
6697 let owner = ""; |
|
6698 |
|
6699 switch (newMode) { |
|
6700 case this.IDENTITY_MODE_DOMAIN_VERIFIED: |
|
6701 host = this.getEffectiveHost(); |
|
6702 owner = gNavigatorBundle.getString("identity.ownerUnknown2"); |
|
6703 verifier = this._identityBox.tooltipText; |
|
6704 break; |
|
6705 case this.IDENTITY_MODE_IDENTIFIED: { |
|
6706 // If it's identified, then we can populate the dialog with credentials |
|
6707 let iData = this.getIdentityData(); |
|
6708 host = this.getEffectiveHost(); |
|
6709 owner = iData.subjectOrg; |
|
6710 verifier = this._identityBox.tooltipText; |
|
6711 |
|
6712 // Build an appropriate supplemental block out of whatever location data we have |
|
6713 if (iData.city) |
|
6714 supplemental += iData.city + "\n"; |
|
6715 if (iData.state && iData.country) |
|
6716 supplemental += gNavigatorBundle.getFormattedString("identity.identified.state_and_country", |
|
6717 [iData.state, iData.country]); |
|
6718 else if (iData.state) // State only |
|
6719 supplemental += iData.state; |
|
6720 else if (iData.country) // Country only |
|
6721 supplemental += iData.country; |
|
6722 break; } |
|
6723 case this.IDENTITY_MODE_CHROMEUI: { |
|
6724 let brandBundle = document.getElementById("bundle_brand"); |
|
6725 let brandShortName = brandBundle.getString("brandShortName"); |
|
6726 this._identityPopupChromeLabel.textContent = gNavigatorBundle.getFormattedString("identity.chrome", |
|
6727 [brandShortName]); |
|
6728 break; } |
|
6729 } |
|
6730 |
|
6731 // Push the appropriate strings out to the UI |
|
6732 this._identityPopupContentHost.textContent = host; |
|
6733 this._identityPopupContentOwner.textContent = owner; |
|
6734 this._identityPopupContentSupp.textContent = supplemental; |
|
6735 this._identityPopupContentVerif.textContent = verifier; |
|
6736 }, |
|
6737 |
|
6738 /** |
|
6739 * Click handler for the identity-box element in primary chrome. |
|
6740 */ |
|
6741 handleIdentityButtonEvent : function(event) { |
|
6742 event.stopPropagation(); |
|
6743 |
|
6744 if ((event.type == "click" && event.button != 0) || |
|
6745 (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE && |
|
6746 event.keyCode != KeyEvent.DOM_VK_RETURN)) { |
|
6747 return; // Left click, space or enter only |
|
6748 } |
|
6749 |
|
6750 // Don't allow left click, space or enter if the location has been modified. |
|
6751 if (gURLBar.getAttribute("pageproxystate") != "valid") { |
|
6752 return; |
|
6753 } |
|
6754 |
|
6755 // Make sure that the display:none style we set in xul is removed now that |
|
6756 // the popup is actually needed |
|
6757 this._identityPopup.hidden = false; |
|
6758 |
|
6759 // Update the popup strings |
|
6760 this.setPopupMessages(this._identityBox.className); |
|
6761 |
|
6762 this.updateSitePermissions(); |
|
6763 |
|
6764 // Add the "open" attribute to the identity box for styling |
|
6765 this._identityBox.setAttribute("open", "true"); |
|
6766 var self = this; |
|
6767 this._identityPopup.addEventListener("popuphidden", function onPopupHidden(e) { |
|
6768 e.currentTarget.removeEventListener("popuphidden", onPopupHidden, false); |
|
6769 self._identityBox.removeAttribute("open"); |
|
6770 }, false); |
|
6771 |
|
6772 // Now open the popup, anchored off the primary chrome element |
|
6773 this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft"); |
|
6774 }, |
|
6775 |
|
6776 onPopupShown : function(event) { |
|
6777 document.getElementById('identity-popup-more-info-button').focus(); |
|
6778 |
|
6779 this._identityPopup.addEventListener("blur", this, true); |
|
6780 this._identityPopup.addEventListener("popuphidden", this); |
|
6781 }, |
|
6782 |
|
6783 onDragStart: function (event) { |
|
6784 if (gURLBar.getAttribute("pageproxystate") != "valid") |
|
6785 return; |
|
6786 |
|
6787 var value = content.location.href; |
|
6788 var urlString = value + "\n" + content.document.title; |
|
6789 var htmlString = "<a href=\"" + value + "\">" + value + "</a>"; |
|
6790 |
|
6791 var dt = event.dataTransfer; |
|
6792 dt.setData("text/x-moz-url", urlString); |
|
6793 dt.setData("text/uri-list", value); |
|
6794 dt.setData("text/plain", value); |
|
6795 dt.setData("text/html", htmlString); |
|
6796 dt.setDragImage(gProxyFavIcon, 16, 16); |
|
6797 }, |
|
6798 |
|
6799 handleEvent: function (event) { |
|
6800 switch (event.type) { |
|
6801 case "blur": |
|
6802 // Focus hasn't moved yet, need to wait until after the blur event. |
|
6803 setTimeout(() => { |
|
6804 if (document.activeElement && |
|
6805 document.activeElement.compareDocumentPosition(this._identityPopup) & |
|
6806 Node.DOCUMENT_POSITION_CONTAINS) |
|
6807 return; |
|
6808 |
|
6809 this._identityPopup.hidePopup(); |
|
6810 }, 0); |
|
6811 break; |
|
6812 case "popuphidden": |
|
6813 this._identityPopup.removeEventListener("blur", this, true); |
|
6814 this._identityPopup.removeEventListener("popuphidden", this); |
|
6815 break; |
|
6816 } |
|
6817 }, |
|
6818 |
|
6819 updateSitePermissions: function () { |
|
6820 while (this._permissionList.hasChildNodes()) |
|
6821 this._permissionList.removeChild(this._permissionList.lastChild); |
|
6822 |
|
6823 let uri = gBrowser.currentURI; |
|
6824 |
|
6825 for (let permission of SitePermissions.listPermissions()) { |
|
6826 let state = SitePermissions.get(uri, permission); |
|
6827 |
|
6828 if (state == SitePermissions.UNKNOWN) |
|
6829 continue; |
|
6830 |
|
6831 let item = this._createPermissionItem(permission, state); |
|
6832 this._permissionList.appendChild(item); |
|
6833 } |
|
6834 |
|
6835 this._permissionsContainer.hidden = !this._permissionList.hasChildNodes(); |
|
6836 }, |
|
6837 |
|
6838 setPermission: function (aPermission, aState) { |
|
6839 if (aState == SitePermissions.getDefault(aPermission)) |
|
6840 SitePermissions.remove(gBrowser.currentURI, aPermission); |
|
6841 else |
|
6842 SitePermissions.set(gBrowser.currentURI, aPermission, aState); |
|
6843 }, |
|
6844 |
|
6845 _createPermissionItem: function (aPermission, aState) { |
|
6846 let menulist = document.createElement("menulist"); |
|
6847 let menupopup = document.createElement("menupopup"); |
|
6848 for (let state of SitePermissions.getAvailableStates(aPermission)) { |
|
6849 let menuitem = document.createElement("menuitem"); |
|
6850 menuitem.setAttribute("value", state); |
|
6851 menuitem.setAttribute("label", SitePermissions.getStateLabel(aPermission, state)); |
|
6852 menupopup.appendChild(menuitem); |
|
6853 } |
|
6854 menulist.appendChild(menupopup); |
|
6855 menulist.setAttribute("value", aState); |
|
6856 menulist.setAttribute("oncommand", "gIdentityHandler.setPermission('" + |
|
6857 aPermission + "', this.value)"); |
|
6858 menulist.setAttribute("id", "identity-popup-permission:" + aPermission); |
|
6859 |
|
6860 let label = document.createElement("label"); |
|
6861 label.setAttribute("flex", "1"); |
|
6862 label.setAttribute("control", menulist.getAttribute("id")); |
|
6863 label.setAttribute("value", SitePermissions.getPermissionLabel(aPermission)); |
|
6864 |
|
6865 let container = document.createElement("hbox"); |
|
6866 container.setAttribute("align", "center"); |
|
6867 container.appendChild(label); |
|
6868 container.appendChild(menulist); |
|
6869 return container; |
|
6870 } |
|
6871 }; |
|
6872 |
|
6873 function getNotificationBox(aWindow) { |
|
6874 var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document); |
|
6875 if (foundBrowser) |
|
6876 return gBrowser.getNotificationBox(foundBrowser) |
|
6877 return null; |
|
6878 }; |
|
6879 |
|
6880 function getTabModalPromptBox(aWindow) { |
|
6881 var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document); |
|
6882 if (foundBrowser) |
|
6883 return gBrowser.getTabModalPromptBox(foundBrowser); |
|
6884 return null; |
|
6885 }; |
|
6886 |
|
6887 /* DEPRECATED */ |
|
6888 function getBrowser() gBrowser; |
|
6889 function getNavToolbox() gNavToolbox; |
|
6890 |
|
6891 let gPrivateBrowsingUI = { |
|
6892 init: function PBUI_init() { |
|
6893 // Do nothing for normal windows |
|
6894 if (!PrivateBrowsingUtils.isWindowPrivate(window)) { |
|
6895 return; |
|
6896 } |
|
6897 |
|
6898 // Disable the Clear Recent History... menu item when in PB mode |
|
6899 // temporary fix until bug 463607 is fixed |
|
6900 document.getElementById("Tools:Sanitize").setAttribute("disabled", "true"); |
|
6901 |
|
6902 if (window.location.href == getBrowserURL()) { |
|
6903 // Adjust the window's title |
|
6904 let docElement = document.documentElement; |
|
6905 if (!PrivateBrowsingUtils.permanentPrivateBrowsing) { |
|
6906 docElement.setAttribute("title", |
|
6907 docElement.getAttribute("title_privatebrowsing")); |
|
6908 docElement.setAttribute("titlemodifier", |
|
6909 docElement.getAttribute("titlemodifier_privatebrowsing")); |
|
6910 } |
|
6911 docElement.setAttribute("privatebrowsingmode", |
|
6912 PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary"); |
|
6913 gBrowser.updateTitlebar(); |
|
6914 |
|
6915 if (PrivateBrowsingUtils.permanentPrivateBrowsing) { |
|
6916 // Adjust the New Window menu entries |
|
6917 [ |
|
6918 { normal: "menu_newNavigator", private: "menu_newPrivateWindow" }, |
|
6919 ].forEach(function(menu) { |
|
6920 let newWindow = document.getElementById(menu.normal); |
|
6921 let newPrivateWindow = document.getElementById(menu.private); |
|
6922 if (newWindow && newPrivateWindow) { |
|
6923 newPrivateWindow.hidden = true; |
|
6924 newWindow.label = newPrivateWindow.label; |
|
6925 newWindow.accessKey = newPrivateWindow.accessKey; |
|
6926 newWindow.command = newPrivateWindow.command; |
|
6927 } |
|
6928 }); |
|
6929 } |
|
6930 } |
|
6931 |
|
6932 if (gURLBar && |
|
6933 !PrivateBrowsingUtils.permanentPrivateBrowsing) { |
|
6934 // Disable switch to tab autocompletion for private windows |
|
6935 // (not for "Always use private browsing" mode) |
|
6936 gURLBar.setAttribute("autocompletesearchparam", ""); |
|
6937 } |
|
6938 } |
|
6939 }; |
|
6940 |
|
6941 let gRemoteTabsUI = { |
|
6942 init: function() { |
|
6943 if (window.location.href != getBrowserURL()) { |
|
6944 return; |
|
6945 } |
|
6946 |
|
6947 let remoteTabs = gPrefService.getBoolPref("browser.tabs.remote"); |
|
6948 let autostart = gPrefService.getBoolPref("browser.tabs.remote.autostart"); |
|
6949 |
|
6950 let newRemoteWindow = document.getElementById("menu_newRemoteWindow"); |
|
6951 let newNonRemoteWindow = document.getElementById("menu_newNonRemoteWindow"); |
|
6952 |
|
6953 if (!remoteTabs) { |
|
6954 newRemoteWindow.hidden = true; |
|
6955 newNonRemoteWindow.hidden = true; |
|
6956 return; |
|
6957 } |
|
6958 |
|
6959 newRemoteWindow.hidden = autostart; |
|
6960 newNonRemoteWindow.hidden = !autostart; |
|
6961 } |
|
6962 }; |
|
6963 |
|
6964 /** |
|
6965 * Switch to a tab that has a given URI, and focusses its browser window. |
|
6966 * If a matching tab is in this window, it will be switched to. Otherwise, other |
|
6967 * windows will be searched. |
|
6968 * |
|
6969 * @param aURI |
|
6970 * URI to search for |
|
6971 * @param aOpenNew |
|
6972 * True to open a new tab and switch to it, if no existing tab is found. |
|
6973 * If no suitable window is found, a new one will be opened. |
|
6974 * @param aOpenParams |
|
6975 * If switching to this URI results in us opening a tab, aOpenParams |
|
6976 * will be the parameter object that gets passed to openUILinkIn. Please |
|
6977 * see the documentation for openUILinkIn to see what parameters can be |
|
6978 * passed via this object. |
|
6979 * @return True if an existing tab was found, false otherwise |
|
6980 */ |
|
6981 function switchToTabHavingURI(aURI, aOpenNew, aOpenParams) { |
|
6982 // Certain URLs can be switched to irrespective of the source or destination |
|
6983 // window being in private browsing mode: |
|
6984 const kPrivateBrowsingWhitelist = new Set([ |
|
6985 "about:customizing", |
|
6986 ]); |
|
6987 // This will switch to the tab in aWindow having aURI, if present. |
|
6988 function switchIfURIInWindow(aWindow) { |
|
6989 // Only switch to the tab if neither the source nor the destination window |
|
6990 // are private and they are not in permanent private browsing mode |
|
6991 if (!kPrivateBrowsingWhitelist.has(aURI.spec) && |
|
6992 (PrivateBrowsingUtils.isWindowPrivate(window) || |
|
6993 PrivateBrowsingUtils.isWindowPrivate(aWindow)) && |
|
6994 !PrivateBrowsingUtils.permanentPrivateBrowsing) { |
|
6995 return false; |
|
6996 } |
|
6997 |
|
6998 let browsers = aWindow.gBrowser.browsers; |
|
6999 for (let i = 0; i < browsers.length; i++) { |
|
7000 let browser = browsers[i]; |
|
7001 if (browser.currentURI.equals(aURI)) { |
|
7002 // Focus the matching window & tab |
|
7003 aWindow.focus(); |
|
7004 aWindow.gBrowser.tabContainer.selectedIndex = i; |
|
7005 return true; |
|
7006 } |
|
7007 } |
|
7008 return false; |
|
7009 } |
|
7010 |
|
7011 // This can be passed either nsIURI or a string. |
|
7012 if (!(aURI instanceof Ci.nsIURI)) |
|
7013 aURI = Services.io.newURI(aURI, null, null); |
|
7014 |
|
7015 let isBrowserWindow = !!window.gBrowser; |
|
7016 |
|
7017 // Prioritise this window. |
|
7018 if (isBrowserWindow && switchIfURIInWindow(window)) |
|
7019 return true; |
|
7020 |
|
7021 let winEnum = Services.wm.getEnumerator("navigator:browser"); |
|
7022 while (winEnum.hasMoreElements()) { |
|
7023 let browserWin = winEnum.getNext(); |
|
7024 // Skip closed (but not yet destroyed) windows, |
|
7025 // and the current window (which was checked earlier). |
|
7026 if (browserWin.closed || browserWin == window) |
|
7027 continue; |
|
7028 if (switchIfURIInWindow(browserWin)) |
|
7029 return true; |
|
7030 } |
|
7031 |
|
7032 // No opened tab has that url. |
|
7033 if (aOpenNew) { |
|
7034 if (isBrowserWindow && isTabEmpty(gBrowser.selectedTab)) |
|
7035 openUILinkIn(aURI.spec, "current", aOpenParams); |
|
7036 else |
|
7037 openUILinkIn(aURI.spec, "tab", aOpenParams); |
|
7038 } |
|
7039 |
|
7040 return false; |
|
7041 } |
|
7042 |
|
7043 let RestoreLastSessionObserver = { |
|
7044 init: function () { |
|
7045 if (SessionStore.canRestoreLastSession && |
|
7046 !PrivateBrowsingUtils.isWindowPrivate(window)) { |
|
7047 Services.obs.addObserver(this, "sessionstore-last-session-cleared", true); |
|
7048 goSetCommandEnabled("Browser:RestoreLastSession", true); |
|
7049 } |
|
7050 }, |
|
7051 |
|
7052 observe: function () { |
|
7053 // The last session can only be restored once so there's |
|
7054 // no way we need to re-enable our menu item. |
|
7055 Services.obs.removeObserver(this, "sessionstore-last-session-cleared"); |
|
7056 goSetCommandEnabled("Browser:RestoreLastSession", false); |
|
7057 }, |
|
7058 |
|
7059 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, |
|
7060 Ci.nsISupportsWeakReference]) |
|
7061 }; |
|
7062 |
|
7063 function restoreLastSession() { |
|
7064 SessionStore.restoreLastSession(); |
|
7065 } |
|
7066 |
|
7067 var TabContextMenu = { |
|
7068 contextTab: null, |
|
7069 updateContextMenu: function updateContextMenu(aPopupMenu) { |
|
7070 this.contextTab = aPopupMenu.triggerNode.localName == "tab" ? |
|
7071 aPopupMenu.triggerNode : gBrowser.selectedTab; |
|
7072 let disabled = gBrowser.tabs.length == 1; |
|
7073 |
|
7074 var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple"); |
|
7075 for (let menuItem of menuItems) |
|
7076 menuItem.disabled = disabled; |
|
7077 |
|
7078 disabled = gBrowser.visibleTabs.length == 1; |
|
7079 menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible"); |
|
7080 for (let menuItem of menuItems) |
|
7081 menuItem.disabled = disabled; |
|
7082 |
|
7083 // Session store |
|
7084 document.getElementById("context_undoCloseTab").disabled = |
|
7085 SessionStore.getClosedTabCount(window) == 0; |
|
7086 |
|
7087 // Only one of pin/unpin should be visible |
|
7088 document.getElementById("context_pinTab").hidden = this.contextTab.pinned; |
|
7089 document.getElementById("context_unpinTab").hidden = !this.contextTab.pinned; |
|
7090 |
|
7091 // Disable "Close Tabs to the Right" if there are no tabs |
|
7092 // following it and hide it when the user rightclicked on a pinned |
|
7093 // tab. |
|
7094 document.getElementById("context_closeTabsToTheEnd").disabled = |
|
7095 gBrowser.getTabsToTheEndFrom(this.contextTab).length == 0; |
|
7096 document.getElementById("context_closeTabsToTheEnd").hidden = this.contextTab.pinned; |
|
7097 |
|
7098 // Disable "Close other Tabs" if there is only one unpinned tab and |
|
7099 // hide it when the user rightclicked on a pinned tab. |
|
7100 let unpinnedTabs = gBrowser.visibleTabs.length - gBrowser._numPinnedTabs; |
|
7101 document.getElementById("context_closeOtherTabs").disabled = unpinnedTabs <= 1; |
|
7102 document.getElementById("context_closeOtherTabs").hidden = this.contextTab.pinned; |
|
7103 |
|
7104 // Hide "Bookmark All Tabs" for a pinned tab. Update its state if visible. |
|
7105 let bookmarkAllTabs = document.getElementById("context_bookmarkAllTabs"); |
|
7106 bookmarkAllTabs.hidden = this.contextTab.pinned; |
|
7107 if (!bookmarkAllTabs.hidden) |
|
7108 PlacesCommandHook.updateBookmarkAllTabsCommand(); |
|
7109 |
|
7110 // Hide "Move to Group" if it's a pinned tab. |
|
7111 document.getElementById("context_tabViewMenu").hidden = |
|
7112 (this.contextTab.pinned || !TabView.firstUseExperienced); |
|
7113 } |
|
7114 }; |
|
7115 |
|
7116 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", |
|
7117 "resource:///modules/devtools/gDevTools.jsm"); |
|
7118 |
|
7119 XPCOMUtils.defineLazyModuleGetter(this, "gDevToolsBrowser", |
|
7120 "resource:///modules/devtools/gDevTools.jsm"); |
|
7121 |
|
7122 Object.defineProperty(this, "HUDService", { |
|
7123 get: function HUDService_getter() { |
|
7124 let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools; |
|
7125 return devtools.require("devtools/webconsole/hudservice"); |
|
7126 }, |
|
7127 configurable: true, |
|
7128 enumerable: true |
|
7129 }); |
|
7130 |
|
7131 // Prompt user to restart the browser in safe mode |
|
7132 function safeModeRestart() |
|
7133 { |
|
7134 // prompt the user to confirm |
|
7135 let promptTitle = gNavigatorBundle.getString("safeModeRestartPromptTitle"); |
|
7136 let promptMessage = |
|
7137 gNavigatorBundle.getString("safeModeRestartPromptMessage"); |
|
7138 let restartText = gNavigatorBundle.getString("safeModeRestartButton"); |
|
7139 let buttonFlags = (Services.prompt.BUTTON_POS_0 * |
|
7140 Services.prompt.BUTTON_TITLE_IS_STRING) + |
|
7141 (Services.prompt.BUTTON_POS_1 * |
|
7142 Services.prompt.BUTTON_TITLE_CANCEL) + |
|
7143 Services.prompt.BUTTON_POS_0_DEFAULT; |
|
7144 |
|
7145 let rv = Services.prompt.confirmEx(window, promptTitle, promptMessage, |
|
7146 buttonFlags, restartText, null, null, |
|
7147 null, {}); |
|
7148 if (rv != 0) |
|
7149 return; |
|
7150 |
|
7151 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"] |
|
7152 .createInstance(Ci.nsISupportsPRBool); |
|
7153 Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); |
|
7154 |
|
7155 if (!cancelQuit.data) { |
|
7156 Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit); |
|
7157 } |
|
7158 } |
|
7159 |
|
7160 /* duplicateTabIn duplicates tab in a place specified by the parameter |where|. |
|
7161 * |
|
7162 * |where| can be: |
|
7163 * "tab" new tab |
|
7164 * "tabshifted" same as "tab" but in background if default is to select new |
|
7165 * tabs, and vice versa |
|
7166 * "window" new window |
|
7167 * |
|
7168 * delta is the offset to the history entry that you want to load. |
|
7169 */ |
|
7170 function duplicateTabIn(aTab, where, delta) { |
|
7171 let newTab = SessionStore.duplicateTab(window, aTab, delta); |
|
7172 |
|
7173 switch (where) { |
|
7174 case "window": |
|
7175 gBrowser.hideTab(newTab); |
|
7176 gBrowser.replaceTabWithWindow(newTab); |
|
7177 break; |
|
7178 case "tabshifted": |
|
7179 // A background tab has been opened, nothing else to do here. |
|
7180 break; |
|
7181 case "tab": |
|
7182 gBrowser.selectedTab = newTab; |
|
7183 break; |
|
7184 } |
|
7185 } |
|
7186 |
|
7187 var Scratchpad = { |
|
7188 openScratchpad: function SP_openScratchpad() { |
|
7189 return this.ScratchpadManager.openScratchpad(); |
|
7190 } |
|
7191 }; |
|
7192 |
|
7193 XPCOMUtils.defineLazyGetter(Scratchpad, "ScratchpadManager", function() { |
|
7194 let tmp = {}; |
|
7195 Cu.import("resource:///modules/devtools/scratchpad-manager.jsm", tmp); |
|
7196 return tmp.ScratchpadManager; |
|
7197 }); |
|
7198 |
|
7199 var ResponsiveUI = { |
|
7200 toggle: function RUI_toggle() { |
|
7201 this.ResponsiveUIManager.toggle(window, gBrowser.selectedTab); |
|
7202 } |
|
7203 }; |
|
7204 |
|
7205 XPCOMUtils.defineLazyGetter(ResponsiveUI, "ResponsiveUIManager", function() { |
|
7206 let tmp = {}; |
|
7207 Cu.import("resource:///modules/devtools/responsivedesign.jsm", tmp); |
|
7208 return tmp.ResponsiveUIManager; |
|
7209 }); |
|
7210 |
|
7211 function openEyedropper() { |
|
7212 var eyedropper = new this.Eyedropper(this); |
|
7213 eyedropper.open(); |
|
7214 } |
|
7215 |
|
7216 Object.defineProperty(this, "Eyedropper", { |
|
7217 get: function() { |
|
7218 let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools; |
|
7219 return devtools.require("devtools/eyedropper/eyedropper").Eyedropper; |
|
7220 }, |
|
7221 configurable: true, |
|
7222 enumerable: true |
|
7223 }); |
|
7224 |
|
7225 XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () { |
|
7226 #ifdef XP_WIN |
|
7227 // Only show resizers on Windows 2000 and XP |
|
7228 return parseFloat(Services.sysinfo.getProperty("version")) < 6; |
|
7229 #else |
|
7230 return false; |
|
7231 #endif |
|
7232 }); |
|
7233 |
|
7234 var MousePosTracker = { |
|
7235 _listeners: new Set(), |
|
7236 _x: 0, |
|
7237 _y: 0, |
|
7238 get _windowUtils() { |
|
7239 delete this._windowUtils; |
|
7240 return this._windowUtils = window.getInterface(Ci.nsIDOMWindowUtils); |
|
7241 }, |
|
7242 |
|
7243 addListener: function (listener) { |
|
7244 if (this._listeners.has(listener)) |
|
7245 return; |
|
7246 |
|
7247 listener._hover = false; |
|
7248 this._listeners.add(listener); |
|
7249 |
|
7250 this._callListener(listener); |
|
7251 }, |
|
7252 |
|
7253 removeListener: function (listener) { |
|
7254 this._listeners.delete(listener); |
|
7255 }, |
|
7256 |
|
7257 handleEvent: function (event) { |
|
7258 var fullZoom = this._windowUtils.fullZoom; |
|
7259 this._x = event.screenX / fullZoom - window.mozInnerScreenX; |
|
7260 this._y = event.screenY / fullZoom - window.mozInnerScreenY; |
|
7261 |
|
7262 this._listeners.forEach(function (listener) { |
|
7263 try { |
|
7264 this._callListener(listener); |
|
7265 } catch (e) { |
|
7266 Cu.reportError(e); |
|
7267 } |
|
7268 }, this); |
|
7269 }, |
|
7270 |
|
7271 _callListener: function (listener) { |
|
7272 let rect = listener.getMouseTargetRect(); |
|
7273 let hover = this._x >= rect.left && |
|
7274 this._x <= rect.right && |
|
7275 this._y >= rect.top && |
|
7276 this._y <= rect.bottom; |
|
7277 |
|
7278 if (hover == listener._hover) |
|
7279 return; |
|
7280 |
|
7281 listener._hover = hover; |
|
7282 |
|
7283 if (hover) { |
|
7284 if (listener.onMouseEnter) |
|
7285 listener.onMouseEnter(); |
|
7286 } else { |
|
7287 if (listener.onMouseLeave) |
|
7288 listener.onMouseLeave(); |
|
7289 } |
|
7290 } |
|
7291 }; |
|
7292 |
|
7293 function focusNextFrame(event) { |
|
7294 let fm = Services.focus; |
|
7295 let dir = event.shiftKey ? fm.MOVEFOCUS_BACKWARDDOC : fm.MOVEFOCUS_FORWARDDOC; |
|
7296 let element = fm.moveFocus(window, null, dir, fm.FLAG_BYKEY); |
|
7297 if (element.ownerDocument == document) |
|
7298 focusAndSelectUrlBar(); |
|
7299 } |
|
7300 let BrowserChromeTest = { |
|
7301 _cb: null, |
|
7302 _ready: false, |
|
7303 markAsReady: function () { |
|
7304 this._ready = true; |
|
7305 if (this._cb) { |
|
7306 this._cb(); |
|
7307 this._cb = null; |
|
7308 } |
|
7309 }, |
|
7310 runWhenReady: function (cb) { |
|
7311 if (this._ready) |
|
7312 cb(); |
|
7313 else |
|
7314 this._cb = cb; |
|
7315 } |
|
7316 }; |
|
7317 |
|
7318 function BrowserOpenNewTabOrWindow(event) { |
|
7319 if (event.shiftKey) { |
|
7320 OpenBrowserWindow(); |
|
7321 } else { |
|
7322 BrowserOpenTab(); |
|
7323 } |
|
7324 } |
|
7325 |
|
7326 let ToolbarIconColor = { |
|
7327 init: function () { |
|
7328 this._initialized = true; |
|
7329 |
|
7330 window.addEventListener("activate", this); |
|
7331 window.addEventListener("deactivate", this); |
|
7332 Services.obs.addObserver(this, "lightweight-theme-styling-update", false); |
|
7333 |
|
7334 // If the window isn't active now, we assume that it has never been active |
|
7335 // before and will soon become active such that inferFromText will be |
|
7336 // called from the initial activate event. |
|
7337 if (Services.focus.activeWindow == window) |
|
7338 this.inferFromText(); |
|
7339 }, |
|
7340 |
|
7341 uninit: function () { |
|
7342 this._initialized = false; |
|
7343 |
|
7344 window.removeEventListener("activate", this); |
|
7345 window.removeEventListener("deactivate", this); |
|
7346 Services.obs.removeObserver(this, "lightweight-theme-styling-update"); |
|
7347 }, |
|
7348 |
|
7349 handleEvent: function (event) { |
|
7350 switch (event.type) { |
|
7351 case "activate": |
|
7352 case "deactivate": |
|
7353 this.inferFromText(); |
|
7354 break; |
|
7355 } |
|
7356 }, |
|
7357 |
|
7358 observe: function (aSubject, aTopic, aData) { |
|
7359 switch (aTopic) { |
|
7360 case "lightweight-theme-styling-update": |
|
7361 // inferFromText needs to run after LightweightThemeConsumer.jsm's |
|
7362 // lightweight-theme-styling-update observer. |
|
7363 setTimeout(() => { this.inferFromText(); }, 0); |
|
7364 break; |
|
7365 } |
|
7366 }, |
|
7367 |
|
7368 inferFromText: function () { |
|
7369 if (!this._initialized) |
|
7370 return; |
|
7371 |
|
7372 function parseRGB(aColorString) { |
|
7373 let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/); |
|
7374 rgb.shift(); |
|
7375 return rgb.map(x => parseInt(x)); |
|
7376 } |
|
7377 |
|
7378 let toolbarSelector = "#navigator-toolbox > toolbar:not([collapsed=true]):not(#addon-bar)"; |
|
7379 #ifdef XP_MACOSX |
|
7380 toolbarSelector += ":not([type=menubar])"; |
|
7381 #endif |
|
7382 |
|
7383 for (let toolbar of document.querySelectorAll(toolbarSelector)) { |
|
7384 let [r, g, b] = parseRGB(getComputedStyle(toolbar).color); |
|
7385 let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b; |
|
7386 if (luminance <= 110) |
|
7387 toolbar.removeAttribute("brighttext"); |
|
7388 else |
|
7389 toolbar.setAttribute("brighttext", "true"); |
|
7390 } |
|
7391 } |
|
7392 } |