browser/metro/base/content/browser.js

changeset 2
7e26c7da4463
equal deleted inserted replaced
-1:000000000000 0:e843f9a9ecec
1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
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 Cc = Components.classes;
7 let Ci = Components.interfaces;
8 let Cu = Components.utils;
9 let Cr = Components.results;
10
11 Cu.import("resource://gre/modules/PageThumbs.jsm");
12
13 // Page for which the start UI is shown
14 const kStartURI = "about:newtab";
15
16 // allow panning after this timeout on pages with registered touch listeners
17 const kTouchTimeout = 300;
18 const kSetInactiveStateTimeout = 100;
19
20 const kTabThumbnailDelayCapture = 500;
21
22 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
23
24 // See grid.xml, we use this to cache style info across loads of the startui.
25 var _richgridTileSizes = {};
26
27 // Override sizeToContent in the main window. It breaks things (bug 565887)
28 window.sizeToContent = function() {
29 Cu.reportError("window.sizeToContent is not allowed in this window");
30 }
31
32 function getTabModalPromptBox(aWindow) {
33 let browser = Browser.getBrowserForWindow(aWindow);
34 return Browser.getTabModalPromptBox(browser);
35 }
36
37 /*
38 * Returns the browser for the currently displayed tab.
39 */
40 function getBrowser() {
41 return Browser.selectedBrowser;
42 }
43
44 var Browser = {
45 _debugEvents: false,
46 _tabs: [],
47 _selectedTab: null,
48 _tabId: 0,
49 windowUtils: window.QueryInterface(Ci.nsIInterfaceRequestor)
50 .getInterface(Ci.nsIDOMWindowUtils),
51
52 get defaultBrowserWidth() {
53 return window.innerWidth;
54 },
55
56 startup: function startup() {
57 var self = this;
58
59 try {
60 messageManager.loadFrameScript("chrome://browser/content/Util.js", true);
61 messageManager.loadFrameScript("chrome://browser/content/contenthandlers/Content.js", true);
62 messageManager.loadFrameScript("chrome://browser/content/contenthandlers/FormHelper.js", true);
63 messageManager.loadFrameScript("chrome://browser/content/library/SelectionPrototype.js", true);
64 messageManager.loadFrameScript("chrome://browser/content/contenthandlers/SelectionHandler.js", true);
65 messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ContextMenuHandler.js", true);
66 messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ConsoleAPIObserver.js", true);
67 messageManager.loadFrameScript("chrome://browser/content/contenthandlers/PluginHelper.js", true);
68 } catch (e) {
69 // XXX whatever is calling startup needs to dump errors!
70 dump("###########" + e + "\n");
71 }
72
73 if (!Services.metro) {
74 // Services.metro is only available on Windows Metro. We want to be able
75 // to test metro on other platforms, too, so we provide a minimal shim.
76 Services.metro = {
77 activationURI: "",
78 pinTileAsync: function () {},
79 unpinTileAsync: function () {}
80 };
81 }
82
83 /* handles dispatching clicks on browser into clicks in content or zooms */
84 Elements.browsers.customDragger = new Browser.MainDragger();
85
86 /* handles web progress management for open browsers */
87 Elements.browsers.webProgress = WebProgress.init();
88
89 // Call InputSourceHelper first so global listeners get called before
90 // we start processing input in TouchModule.
91 InputSourceHelper.init();
92 ClickEventHandler.init();
93
94 TouchModule.init();
95 GestureModule.init();
96 BrowserTouchHandler.init();
97 PopupBlockerObserver.init();
98 APZCObserver.init();
99
100 // Init the touch scrollbox
101 this.contentScrollbox = Elements.browsers;
102 this.contentScrollboxScroller = {
103 scrollBy: function(aDx, aDy) {
104 let view = getBrowser().getRootView();
105 view.scrollBy(aDx, aDy);
106 },
107
108 scrollTo: function(aX, aY) {
109 let view = getBrowser().getRootView();
110 view.scrollTo(aX, aY);
111 },
112
113 getPosition: function(aScrollX, aScrollY) {
114 let view = getBrowser().getRootView();
115 let scroll = view.getPosition();
116 aScrollX.value = scroll.x;
117 aScrollY.value = scroll.y;
118 }
119 };
120
121 ContentAreaObserver.init();
122
123 function fullscreenHandler() {
124 if (Browser.selectedBrowser.contentWindow.document.mozFullScreenElement)
125 Elements.stack.setAttribute("fullscreen", "true");
126 else
127 Elements.stack.removeAttribute("fullscreen");
128 }
129 window.addEventListener("mozfullscreenchange", fullscreenHandler, true);
130
131 BrowserUI.init();
132
133 window.controllers.appendController(this);
134 window.controllers.appendController(BrowserUI);
135
136 Services.obs.addObserver(SessionHistoryObserver, "browser:purge-session-history", false);
137
138 window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();
139
140 Elements.browsers.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false);
141
142 // Make sure we're online before attempting to load
143 Util.forceOnline();
144
145 // If this is an intial window launch the commandline handler passes us the default
146 // page as an argument.
147 let commandURL = null;
148 try {
149 let argsObj = window.arguments[0].wrappedJSObject;
150 if (argsObj && argsObj.pageloadURL) {
151 // Talos tp-cmdline parameter
152 commandURL = argsObj.pageloadURL;
153 } else if (window.arguments && window.arguments[0]) {
154 // BrowserCLH paramerter
155 commandURL = window.arguments[0];
156 }
157 } catch (ex) {
158 Util.dumpLn(ex);
159 }
160
161 messageManager.addMessageListener("DOMLinkAdded", this);
162 messageManager.addMessageListener("Browser:FormSubmit", this);
163 messageManager.addMessageListener("Browser:CanUnload:Return", this);
164 messageManager.addMessageListener("scroll", this);
165 messageManager.addMessageListener("Browser:CertException", this);
166 messageManager.addMessageListener("Browser:BlockedSite", this);
167
168 Task.spawn(function() {
169 // Activation URIs come from protocol activations, secondary tiles, and file activations
170 let activationURI = yield this.getShortcutOrURI(Services.metro.activationURI);
171
172 let self = this;
173 function loadStartupURI() {
174 if (activationURI) {
175 let webNav = Ci.nsIWebNavigation;
176 let flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
177 webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
178 self.addTab(activationURI, true, null, { flags: flags });
179 } else {
180 let uri = commandURL || Browser.getHomePage();
181 self.addTab(uri, true);
182 }
183 }
184
185 // Should we restore the previous session (crash or some other event)
186 let ss = Cc["@mozilla.org/browser/sessionstore;1"]
187 .getService(Ci.nsISessionStore);
188 let shouldRestore = ss.shouldRestore();
189 if (shouldRestore) {
190 let bringFront = false;
191 // First open any commandline URLs, except the homepage
192 if (activationURI && activationURI != kStartURI) {
193 this.addTab(activationURI, true, null, { flags: Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP });
194 } else if (commandURL && commandURL != kStartURI) {
195 this.addTab(commandURL, true);
196 } else {
197 bringFront = true;
198 // Initial window resizes call functions that assume a tab is in the tab list
199 // and restored tabs are added too late. We add a dummy to to satisfy the resize
200 // code and then remove the dummy after the session has been restored.
201 let dummy = this.addTab("about:blank", true);
202 let dummyCleanup = {
203 observe: function(aSubject, aTopic, aData) {
204 Services.obs.removeObserver(dummyCleanup, "sessionstore-windows-restored");
205 if (aData == "fail")
206 loadStartupURI();
207 dummy.chromeTab.ignoreUndo = true;
208 Browser.closeTab(dummy, { forceClose: true });
209 }
210 };
211 Services.obs.addObserver(dummyCleanup, "sessionstore-windows-restored", false);
212 }
213 ss.restoreLastSession(bringFront);
214 } else {
215 loadStartupURI();
216 }
217
218 // Notify about our input type
219 InputSourceHelper.fireUpdate();
220
221 // Broadcast a UIReady message so add-ons know we are finished with startup
222 let event = document.createEvent("Events");
223 event.initEvent("UIReady", true, false);
224 window.dispatchEvent(event);
225 }.bind(this));
226 },
227
228 shutdown: function shutdown() {
229 APZCObserver.shutdown();
230 BrowserUI.uninit();
231 ClickEventHandler.uninit();
232 ContentAreaObserver.shutdown();
233 Appbar.shutdown();
234
235 messageManager.removeMessageListener("Browser:FormSubmit", this);
236 messageManager.removeMessageListener("scroll", this);
237 messageManager.removeMessageListener("Browser:CertException", this);
238 messageManager.removeMessageListener("Browser:BlockedSite", this);
239
240 Services.obs.removeObserver(SessionHistoryObserver, "browser:purge-session-history");
241
242 window.controllers.removeController(this);
243 window.controllers.removeController(BrowserUI);
244 },
245
246 getHomePage: function getHomePage(aOptions) {
247 aOptions = aOptions || { useDefault: false };
248
249 let url = kStartURI;
250 try {
251 let prefs = aOptions.useDefault ? Services.prefs.getDefaultBranch(null) : Services.prefs;
252 url = prefs.getComplexValue("browser.startup.homepage", Ci.nsIPrefLocalizedString).data;
253 }
254 catch(e) { }
255
256 return url;
257 },
258
259 get browsers() {
260 return this._tabs.map(function(tab) { return tab.browser; });
261 },
262
263 /**
264 * Load a URI in the current tab, or a new tab if necessary.
265 * @param aURI String
266 * @param aParams Object with optional properties that will be passed to loadURIWithFlags:
267 * flags, referrerURI, charset, postData.
268 */
269 loadURI: function loadURI(aURI, aParams) {
270 let browser = this.selectedBrowser;
271
272 // We need to keep about: pages opening in new "local" tabs. We also want to spawn
273 // new "remote" tabs if opening web pages from a "local" about: page.
274 dump("loadURI=" + aURI + "\ncurrentURI=" + browser.currentURI.spec + "\n");
275
276 let params = aParams || {};
277 try {
278 let flags = params.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
279 let postData = ("postData" in params && params.postData) ? params.postData.value : null;
280 let referrerURI = "referrerURI" in params ? params.referrerURI : null;
281 let charset = "charset" in params ? params.charset : null;
282 dump("loading tab: " + aURI + "\n");
283 browser.loadURIWithFlags(aURI, flags, referrerURI, charset, postData);
284 } catch(e) {
285 dump("Error: " + e + "\n");
286 }
287 },
288
289 /**
290 * Determine if the given URL is a shortcut/keyword and, if so, expand it
291 * @param aURL String
292 * @param aPostDataRef Out param contains any required post data for a search
293 * @return {Promise}
294 * @result the expanded shortcut, or the original URL if not a shortcut
295 */
296 getShortcutOrURI: function getShortcutOrURI(aURL, aPostDataRef) {
297 return Task.spawn(function() {
298 if (!aURL)
299 throw new Task.Result(aURL);
300
301 let shortcutURL = null;
302 let keyword = aURL;
303 let param = "";
304
305 let offset = aURL.indexOf(" ");
306 if (offset > 0) {
307 keyword = aURL.substr(0, offset);
308 param = aURL.substr(offset + 1);
309 }
310
311 if (!aPostDataRef)
312 aPostDataRef = {};
313
314 let engine = Services.search.getEngineByAlias(keyword);
315 if (engine) {
316 let submission = engine.getSubmission(param);
317 aPostDataRef.value = submission.postData;
318 throw new Task.Result(submission.uri.spec);
319 }
320
321 try {
322 [shortcutURL, aPostDataRef.value] = PlacesUtils.getURLAndPostDataForKeyword(keyword);
323 } catch (e) {}
324
325 if (!shortcutURL)
326 throw new Task.Result(aURL);
327
328 let postData = "";
329 if (aPostDataRef.value)
330 postData = unescape(aPostDataRef.value);
331
332 if (/%s/i.test(shortcutURL) || /%s/i.test(postData)) {
333 let charset = "";
334 const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
335 let matches = shortcutURL.match(re);
336 if (matches)
337 [, shortcutURL, charset] = matches;
338 else {
339 // Try to get the saved character-set.
340 try {
341 // makeURI throws if URI is invalid.
342 // Will return an empty string if character-set is not found.
343 charset = yield PlacesUtils.getCharsetForURI(Util.makeURI(shortcutURL));
344 } catch (e) { dump("--- error " + e + "\n"); }
345 }
346
347 let encodedParam = "";
348 if (charset)
349 encodedParam = escape(convertFromUnicode(charset, param));
350 else // Default charset is UTF-8
351 encodedParam = encodeURIComponent(param);
352
353 shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
354
355 if (/%s/i.test(postData)) // POST keyword
356 aPostDataRef.value = getPostDataStream(postData, param, encodedParam, "application/x-www-form-urlencoded");
357 } else if (param) {
358 // This keyword doesn't take a parameter, but one was provided. Just return
359 // the original URL.
360 aPostDataRef.value = null;
361
362 throw new Task.Result(aURL);
363 }
364
365 throw new Task.Result(shortcutURL);
366 });
367 },
368
369 /**
370 * Return the currently active <browser> object
371 */
372 get selectedBrowser() {
373 return (this._selectedTab && this._selectedTab.browser);
374 },
375
376 get tabs() {
377 return this._tabs;
378 },
379
380 getTabModalPromptBox: function(aBrowser) {
381 let browser = (aBrowser || getBrowser());
382 let stack = browser.parentNode;
383 let self = this;
384
385 let promptBox = {
386 appendPrompt : function(args, onCloseCallback) {
387 let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
388 newPrompt.setAttribute("promptType", args.promptType);
389 stack.appendChild(newPrompt);
390 browser.setAttribute("tabmodalPromptShowing", true);
391 newPrompt.clientTop; // style flush to assure binding is attached
392
393 let tab = self.getTabForBrowser(browser);
394 tab = tab.chromeTab;
395
396 newPrompt.metroInit(args, tab, onCloseCallback);
397 return newPrompt;
398 },
399
400 removePrompt : function(aPrompt) {
401 stack.removeChild(aPrompt);
402
403 let prompts = this.listPrompts();
404 if (prompts.length) {
405 let prompt = prompts[prompts.length - 1];
406 prompt.Dialog.setDefaultFocus();
407 } else {
408 browser.removeAttribute("tabmodalPromptShowing");
409 browser.focus();
410 }
411 },
412
413 listPrompts : function(aPrompt) {
414 let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
415 // NodeList --> real JS array
416 let prompts = Array.slice(els);
417 return prompts;
418 },
419 };
420
421 return promptBox;
422 },
423
424 getBrowserForWindowId: function getBrowserForWindowId(aWindowId) {
425 for (let i = 0; i < this.browsers.length; i++) {
426 if (this.browsers[i].contentWindowId == aWindowId)
427 return this.browsers[i];
428 }
429 return null;
430 },
431
432 getBrowserForWindow: function(aWindow) {
433 let windowID = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
434 .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
435 return this.getBrowserForWindowId(windowID);
436 },
437
438 getTabForBrowser: function getTabForBrowser(aBrowser) {
439 let tabs = this._tabs;
440 for (let i = 0; i < tabs.length; i++) {
441 if (tabs[i].browser == aBrowser)
442 return tabs[i];
443 }
444 return null;
445 },
446
447 getTabAtIndex: function getTabAtIndex(index) {
448 if (index >= this._tabs.length || index < 0)
449 return null;
450 return this._tabs[index];
451 },
452
453 getTabFromChrome: function getTabFromChrome(chromeTab) {
454 for (var t = 0; t < this._tabs.length; t++) {
455 if (this._tabs[t].chromeTab == chromeTab)
456 return this._tabs[t];
457 }
458 return null;
459 },
460
461 createTabId: function createTabId() {
462 return this._tabId++;
463 },
464
465 /**
466 * Create a new tab and add it to the tab list.
467 *
468 * If you are opening a new foreground tab in response to a user action, use
469 * BrowserUI.addAndShowTab which will also show the tab strip.
470 *
471 * @param aURI String specifying the URL to load.
472 * @param aBringFront Boolean (optional) Open the new tab in the foreground?
473 * @param aOwner Tab object (optional) The "parent" of the new tab.
474 * This is the tab responsible for opening the new tab. When the new tab
475 * is closed, we will return to a parent or "sibling" tab if possible.
476 * @param aParams Object (optional) with optional properties:
477 * index: Number specifying where in the tab list to insert the new tab.
478 * private: If true, the new tab should be have Private Browsing active.
479 * flags, postData, charset, referrerURI: See loadURIWithFlags.
480 */
481 addTab: function browser_addTab(aURI, aBringFront, aOwner, aParams) {
482 let params = aParams || {};
483
484 if (aOwner && !('index' in params)) {
485 // Position the new tab to the right of its owner...
486 params.index = this._tabs.indexOf(aOwner) + 1;
487 // ...and to the right of any siblings.
488 while (this._tabs[params.index] && this._tabs[params.index].owner == aOwner) {
489 params.index++;
490 }
491 }
492
493 let newTab = new Tab(aURI, params, aOwner);
494
495 if (params.index >= 0) {
496 this._tabs.splice(params.index, 0, newTab);
497 } else {
498 this._tabs.push(newTab);
499 }
500
501 if (aBringFront)
502 this.selectedTab = newTab;
503
504 this._announceNewTab(newTab);
505 return newTab;
506 },
507
508 closeTab: function closeTab(aTab, aOptions) {
509 let tab = aTab instanceof XULElement ? this.getTabFromChrome(aTab) : aTab;
510 if (!tab) {
511 return;
512 }
513
514 if (aOptions && "forceClose" in aOptions && aOptions.forceClose) {
515 this._doCloseTab(tab);
516 return;
517 }
518
519 tab.browser.messageManager.sendAsyncMessage("Browser:CanUnload", {});
520 },
521
522 savePage: function() {
523 ContentAreaUtils.saveDocument(this.selectedBrowser.contentWindow.document);
524 },
525
526 /*
527 * helper for addTab related methods. Fires events related to
528 * new tab creation.
529 */
530 _announceNewTab: function (aTab) {
531 let event = document.createEvent("UIEvents");
532 event.initUIEvent("TabOpen", true, false, window, 0);
533 aTab.chromeTab.dispatchEvent(event);
534 aTab.browser.messageManager.sendAsyncMessage("Browser:TabOpen");
535 },
536
537 _doCloseTab: function _doCloseTab(aTab) {
538 if (this._tabs.length === 1) {
539 Browser.addTab(this.getHomePage());
540 }
541
542 let nextTab = this.getNextTab(aTab);
543
544 // Tabs owned by the closed tab are now orphaned.
545 this._tabs.forEach(function(item, index, array) {
546 if (item.owner == aTab)
547 item.owner = null;
548 });
549
550 // tray tab
551 let event = document.createEvent("Events");
552 event.initEvent("TabClose", true, false);
553 aTab.chromeTab.dispatchEvent(event);
554
555 // tab window
556 event = document.createEvent("Events");
557 event.initEvent("TabClose", true, false);
558 aTab.browser.contentWindow.dispatchEvent(event);
559
560 aTab.browser.messageManager.sendAsyncMessage("Browser:TabClose");
561
562 let container = aTab.chromeTab.parentNode;
563 aTab.destroy();
564 this._tabs.splice(this._tabs.indexOf(aTab), 1);
565
566 this.selectedTab = nextTab;
567
568 event = document.createEvent("Events");
569 event.initEvent("TabRemove", true, false);
570 container.dispatchEvent(event);
571 },
572
573 getNextTab: function getNextTab(aTab) {
574 let tabIndex = this._tabs.indexOf(aTab);
575 if (tabIndex == -1)
576 return null;
577
578 if (this._selectedTab == aTab || this._selectedTab.chromeTab.hasAttribute("closing")) {
579 let nextTabIndex = tabIndex + 1;
580 let nextTab = null;
581
582 while (nextTabIndex < this._tabs.length && (!nextTab || nextTab.chromeTab.hasAttribute("closing"))) {
583 nextTab = this.getTabAtIndex(nextTabIndex);
584 nextTabIndex++;
585 }
586
587 nextTabIndex = tabIndex - 1;
588 while (nextTabIndex >= 0 && (!nextTab || nextTab.chromeTab.hasAttribute("closing"))) {
589 nextTab = this.getTabAtIndex(nextTabIndex);
590 nextTabIndex--;
591 }
592
593 if (!nextTab || nextTab.chromeTab.hasAttribute("closing"))
594 return null;
595
596 // If the next tab is not a sibling, switch back to the parent.
597 if (aTab.owner && nextTab.owner != aTab.owner)
598 nextTab = aTab.owner;
599
600 if (!nextTab)
601 return null;
602
603 return nextTab;
604 }
605
606 return this._selectedTab;
607 },
608
609 get selectedTab() {
610 return this._selectedTab;
611 },
612
613 set selectedTab(tab) {
614 if (tab instanceof XULElement)
615 tab = this.getTabFromChrome(tab);
616
617 if (!tab)
618 return;
619
620 if (this._selectedTab == tab) {
621 // Deck does not update its selectedIndex when children
622 // are removed. See bug 602708
623 Elements.browsers.selectedPanel = tab.notification;
624 return;
625 }
626
627 let isFirstTab = this._selectedTab == null;
628 let lastTab = this._selectedTab;
629 let browser = tab.browser;
630
631 this._selectedTab = tab;
632
633 if (lastTab)
634 lastTab.active = false;
635
636 if (tab)
637 tab.active = true;
638
639 BrowserUI.update();
640
641 if (isFirstTab) {
642 BrowserUI._titleChanged(browser);
643 } else {
644 // Update all of our UI to reflect the new tab's location
645 BrowserUI.updateURI();
646
647 let event = document.createEvent("Events");
648 event.initEvent("TabSelect", true, false);
649 event.lastTab = lastTab;
650 tab.chromeTab.dispatchEvent(event);
651 }
652
653 tab.lastSelected = Date.now();
654 },
655
656 supportsCommand: function(cmd) {
657 return false;
658 },
659
660 isCommandEnabled: function(cmd) {
661 return false;
662 },
663
664 doCommand: function(cmd) {
665 },
666
667 getNotificationBox: function getNotificationBox(aBrowser) {
668 let browser = aBrowser || this.selectedBrowser;
669 return browser.parentNode.parentNode;
670 },
671
672 /**
673 * Handle cert exception message from content.
674 */
675 _handleCertException: function _handleCertException(aMessage) {
676 let json = aMessage.json;
677 if (json.action == "leave") {
678 // Get the start page from the *default* pref branch, not the user's
679 let url = Browser.getHomePage({ useDefault: true });
680 this.loadURI(url);
681 } else {
682 // Handle setting an cert exception and reloading the page
683 try {
684 // Add a new SSL exception for this URL
685 let uri = Services.io.newURI(json.url, null, null);
686 let sslExceptions = new SSLExceptions();
687
688 if (json.action == "permanent")
689 sslExceptions.addPermanentException(uri, window);
690 else
691 sslExceptions.addTemporaryException(uri, window);
692 } catch (e) {
693 dump("EXCEPTION handle content command: " + e + "\n" );
694 }
695
696 // Automatically reload after the exception was added
697 aMessage.target.reload();
698 }
699 },
700
701 /**
702 * Handle blocked site message from content.
703 */
704 _handleBlockedSite: function _handleBlockedSite(aMessage) {
705 let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
706 let json = aMessage.json;
707 switch (json.action) {
708 case "leave": {
709 // Get the start page from the *default* pref branch, not the user's
710 let url = Browser.getHomePage({ useDefault: true });
711 this.loadURI(url);
712 break;
713 }
714 case "report-malware": {
715 // Get the stop badware "why is this blocked" report url, append the current url, and go there.
716 try {
717 let reportURL = formatter.formatURLPref("browser.safebrowsing.malware.reportURL");
718 reportURL += json.url;
719 this.loadURI(reportURL);
720 } catch (e) {
721 Cu.reportError("Couldn't get malware report URL: " + e);
722 }
723 break;
724 }
725 case "report-phishing": {
726 // It's a phishing site, just link to the generic information page
727 let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
728 this.loadURI(url + "phishing-malware");
729 break;
730 }
731 }
732 },
733
734 pinSite: function browser_pinSite() {
735 // Get a path to our app tile
736 var file = Components.classes["@mozilla.org/file/directory_service;1"].
737 getService(Components.interfaces.nsIProperties).
738 get("CurProcD", Components.interfaces.nsIFile);
739 // Get rid of the current working directory's metro subidr
740 file = file.parent;
741 file.append("tileresources");
742 file.append("VisualElements_logo.png");
743 var ios = Components.classes["@mozilla.org/network/io-service;1"].
744 getService(Components.interfaces.nsIIOService);
745 var uriSpec = ios.newFileURI(file).spec;
746 Services.metro.pinTileAsync(this._currentPageTileID,
747 Browser.selectedBrowser.contentTitle, // short name
748 Browser.selectedBrowser.contentTitle, // display name
749 "-url " + Browser.selectedBrowser.currentURI.spec,
750 uriSpec, uriSpec);
751 },
752
753 get _currentPageTileID() {
754 // We use a unique ID per URL, so just use an MD5 hash of the URL as the ID.
755 let hasher = Cc["@mozilla.org/security/hash;1"].
756 createInstance(Ci.nsICryptoHash);
757 hasher.init(Ci.nsICryptoHash.MD5);
758 let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
759 createInstance(Ci.nsIStringInputStream);
760 stringStream.data = Browser.selectedBrowser.currentURI.spec;
761 hasher.updateFromStream(stringStream, -1);
762 let hashASCII = hasher.finish(true);
763 // Replace '/' with a valid filesystem character
764 return ("FFTileID_" + hashASCII).replace('/', '_', 'g');
765 },
766
767 unpinSite: function browser_unpinSite() {
768 if (!Services.metro.immersive)
769 return;
770
771 Services.metro.unpinTileAsync(this._currentPageTileID);
772 },
773
774 isSitePinned: function browser_isSitePinned() {
775 if (!Services.metro.immersive)
776 return false;
777
778 return Services.metro.isTilePinned(this._currentPageTileID);
779 },
780
781 starSite: function browser_starSite(callback) {
782 let uri = this.selectedBrowser.currentURI;
783 let title = this.selectedBrowser.contentTitle;
784
785 Bookmarks.addForURI(uri, title, callback);
786 },
787
788 unstarSite: function browser_unstarSite(callback) {
789 let uri = this.selectedBrowser.currentURI;
790 Bookmarks.removeForURI(uri, callback);
791 },
792
793 isSiteStarredAsync: function browser_isSiteStarredAsync(callback) {
794 let uri = this.selectedBrowser.currentURI;
795 Bookmarks.isURIBookmarked(uri, callback);
796 },
797
798 /**
799 * Convenience function for getting the scrollbox position off of a
800 * scrollBoxObject interface. Returns the actual values instead of the
801 * wrapping objects.
802 *
803 * @param scroller a scrollBoxObject on which to call scroller.getPosition()
804 */
805 getScrollboxPosition: function getScrollboxPosition(scroller) {
806 let x = {};
807 let y = {};
808 scroller.getPosition(x, y);
809 return new Point(x.value, y.value);
810 },
811
812 forceChromeReflow: function forceChromeReflow() {
813 let dummy = getComputedStyle(document.documentElement, "").width;
814 },
815
816 receiveMessage: function receiveMessage(aMessage) {
817 let json = aMessage.json;
818 let browser = aMessage.target;
819
820 switch (aMessage.name) {
821 case "DOMLinkAdded": {
822 // checks for an icon to use for a web app
823 // apple-touch-icon size is 57px and default size is 16px
824 let rel = json.rel.toLowerCase().split(" ");
825 if (rel.indexOf("icon") != -1) {
826 // We use the sizes attribute if available
827 // see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon
828 let size = 16;
829 if (json.sizes) {
830 let sizes = json.sizes.toLowerCase().split(" ");
831 sizes.forEach(function(item) {
832 if (item != "any") {
833 let [w, h] = item.split("x");
834 size = Math.max(Math.min(w, h), size);
835 }
836 });
837 }
838 if (size > browser.appIcon.size) {
839 browser.appIcon.href = json.href;
840 browser.appIcon.size = size;
841 }
842 }
843 else if ((rel.indexOf("apple-touch-icon") != -1) && (browser.appIcon.size < 57)) {
844 // XXX should we support apple-touch-icon-precomposed ?
845 // see http://developer.apple.com/safari/library/documentation/appleapplications/reference/safariwebcontent/configuringwebapplications/configuringwebapplications.html
846 browser.appIcon.href = json.href;
847 browser.appIcon.size = 57;
848 }
849 break;
850 }
851 case "Browser:FormSubmit":
852 browser.lastLocation = null;
853 break;
854
855 case "Browser:CanUnload:Return": {
856 if (json.permit) {
857 let tab = this.getTabForBrowser(browser);
858 BrowserUI.animateClosingTab(tab);
859 }
860 break;
861 }
862 case "Browser:CertException":
863 this._handleCertException(aMessage);
864 break;
865 case "Browser:BlockedSite":
866 this._handleBlockedSite(aMessage);
867 break;
868 }
869 },
870 };
871
872 Browser.MainDragger = function MainDragger() {
873 this._horizontalScrollbar = document.getElementById("horizontal-scroller");
874 this._verticalScrollbar = document.getElementById("vertical-scroller");
875 this._scrollScales = { x: 0, y: 0 };
876
877 Elements.browsers.addEventListener("PanBegin", this, false);
878 Elements.browsers.addEventListener("PanFinished", this, false);
879 };
880
881 Browser.MainDragger.prototype = {
882 isDraggable: function isDraggable(target, scroller) {
883 return { x: true, y: true };
884 },
885
886 dragStart: function dragStart(clientX, clientY, target, scroller) {
887 let browser = getBrowser();
888 let bcr = browser.getBoundingClientRect();
889 this._contentView = browser.getViewAt(clientX - bcr.left, clientY - bcr.top);
890 },
891
892 dragStop: function dragStop(dx, dy, scroller) {
893 if (this._contentView && this._contentView._updateCacheViewport)
894 this._contentView._updateCacheViewport();
895 this._contentView = null;
896 },
897
898 dragMove: function dragMove(dx, dy, scroller, aIsKinetic) {
899 let doffset = new Point(dx, dy);
900
901 this._panContent(doffset);
902
903 if (aIsKinetic && doffset.x != 0)
904 return false;
905
906 this._updateScrollbars();
907
908 return !doffset.equals(dx, dy);
909 },
910
911 handleEvent: function handleEvent(aEvent) {
912 let browser = getBrowser();
913 switch (aEvent.type) {
914 case "PanBegin": {
915 let width = ContentAreaObserver.width, height = ContentAreaObserver.height;
916 let contentWidth = browser.contentDocumentWidth * browser.scale;
917 let contentHeight = browser.contentDocumentHeight * browser.scale;
918
919 // Allow a small margin on both sides to prevent adding scrollbars
920 // on small viewport approximation
921 const ALLOWED_MARGIN = 5;
922 const SCROLL_CORNER_SIZE = 8;
923 this._scrollScales = {
924 x: (width + ALLOWED_MARGIN) < contentWidth ? (width - SCROLL_CORNER_SIZE) / contentWidth : 0,
925 y: (height + ALLOWED_MARGIN) < contentHeight ? (height - SCROLL_CORNER_SIZE) / contentHeight : 0
926 }
927
928 this._showScrollbars();
929 break;
930 }
931 case "PanFinished":
932 this._hideScrollbars();
933
934 // Update the scroll position of the content
935 browser._updateCSSViewport();
936 break;
937 }
938 },
939
940 _panContent: function md_panContent(aOffset) {
941 if (this._contentView && !this._contentView.isRoot()) {
942 this._panContentView(this._contentView, aOffset);
943 // XXX we may need to have "escape borders" for iframe panning
944 // XXX does not deal with scrollables within scrollables
945 }
946 // Do content panning
947 this._panContentView(getBrowser().getRootView(), aOffset);
948 },
949
950 /** Pan scroller by the given amount. Updates doffset with leftovers. */
951 _panContentView: function _panContentView(contentView, doffset) {
952 let pos0 = contentView.getPosition();
953 contentView.scrollBy(doffset.x, doffset.y);
954 let pos1 = contentView.getPosition();
955 doffset.subtract(pos1.x - pos0.x, pos1.y - pos0.y);
956 },
957
958 _updateScrollbars: function _updateScrollbars() {
959 let scaleX = this._scrollScales.x, scaleY = this._scrollScales.y;
960 let contentScroll = Browser.getScrollboxPosition(Browser.contentScrollboxScroller);
961
962 if (scaleX)
963 this._horizontalScrollbar.style.MozTransform =
964 "translateX(" + Math.round(contentScroll.x * scaleX) + "px)";
965
966 if (scaleY) {
967 let y = Math.round(contentScroll.y * scaleY);
968 let x = 0;
969
970 this._verticalScrollbar.style.MozTransform =
971 "translate(" + x + "px," + y + "px)";
972 }
973 },
974
975 _showScrollbars: function _showScrollbars() {
976 this._updateScrollbars();
977 let scaleX = this._scrollScales.x, scaleY = this._scrollScales.y;
978 if (scaleX) {
979 this._horizontalScrollbar.width = ContentAreaObserver.width * scaleX;
980 this._horizontalScrollbar.setAttribute("panning", "true");
981 }
982
983 if (scaleY) {
984 this._verticalScrollbar.height = ContentAreaObserver.height * scaleY;
985 this._verticalScrollbar.setAttribute("panning", "true");
986 }
987 },
988
989 _hideScrollbars: function _hideScrollbars() {
990 this._scrollScales.x = 0;
991 this._scrollScales.y = 0;
992 this._horizontalScrollbar.removeAttribute("panning");
993 this._verticalScrollbar.removeAttribute("panning");
994 this._horizontalScrollbar.removeAttribute("width");
995 this._verticalScrollbar.removeAttribute("height");
996 this._horizontalScrollbar.style.MozTransform = "";
997 this._verticalScrollbar.style.MozTransform = "";
998 }
999 };
1000
1001
1002 function nsBrowserAccess() { }
1003
1004 nsBrowserAccess.prototype = {
1005 QueryInterface: function(aIID) {
1006 if (aIID.equals(Ci.nsIBrowserDOMWindow) || aIID.equals(Ci.nsISupports))
1007 return this;
1008 throw Cr.NS_NOINTERFACE;
1009 },
1010
1011 _getOpenAction: function _getOpenAction(aURI, aOpener, aWhere, aContext) {
1012 let where = aWhere;
1013 /*
1014 * aWhere:
1015 * OPEN_DEFAULTWINDOW: default action
1016 * OPEN_CURRENTWINDOW: current window/tab
1017 * OPEN_NEWWINDOW: not allowed, converted to newtab below
1018 * OPEN_NEWTAB: open a new tab
1019 * OPEN_SWITCHTAB: open in an existing tab if it matches, otherwise open
1020 * a new tab. afaict we always open these in the current tab.
1021 */
1022 if (where == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
1023 // query standard browser prefs indicating what to do for default action
1024 switch (aContext) {
1025 // indicates this is an open request from a 3rd party app.
1026 case Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL :
1027 where = Services.prefs.getIntPref("browser.link.open_external");
1028 break;
1029 // internal request
1030 default :
1031 where = Services.prefs.getIntPref("browser.link.open_newwindow");
1032 }
1033 }
1034 if (where == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW) {
1035 Util.dumpLn("Invalid request - we can't open links in new windows.");
1036 where = Ci.nsIBrowserDOMWindow.OPEN_NEWTAB;
1037 }
1038 return where;
1039 },
1040
1041 _getBrowser: function _getBrowser(aURI, aOpener, aWhere, aContext) {
1042 let isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
1043 // We don't allow externals apps opening chrome docs
1044 if (isExternal && aURI && aURI.schemeIs("chrome"))
1045 return null;
1046
1047 let location;
1048 let browser;
1049 let loadflags = isExternal ?
1050 Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
1051 Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
1052 let openAction = this._getOpenAction(aURI, aOpener, aWhere, aContext);
1053
1054 if (openAction == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
1055 let owner = isExternal ? null : Browser.selectedTab;
1056 let tab = BrowserUI.openLinkInNewTab("about:blank", true, owner);
1057 browser = tab.browser;
1058 } else {
1059 browser = Browser.selectedBrowser;
1060 }
1061
1062 try {
1063 let referrer;
1064 if (aURI && browser) {
1065 if (aOpener) {
1066 location = aOpener.location;
1067 referrer = Services.io.newURI(location, null, null);
1068 }
1069 browser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null);
1070 }
1071 browser.focus();
1072 } catch(e) { }
1073
1074 return browser;
1075 },
1076
1077 openURI: function browser_openURI(aURI, aOpener, aWhere, aContext) {
1078 let browser = this._getBrowser(aURI, aOpener, aWhere, aContext);
1079 return browser ? browser.contentWindow : null;
1080 },
1081
1082 openURIInFrame: function browser_openURIInFrame(aURI, aOpener, aWhere, aContext) {
1083 let browser = this._getBrowser(aURI, aOpener, aWhere, aContext);
1084 return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null;
1085 },
1086
1087 isTabContentWindow: function(aWindow) {
1088 return Browser.browsers.some(function (browser) browser.contentWindow == aWindow);
1089 },
1090
1091 get contentWindow() {
1092 return Browser.selectedBrowser.contentWindow;
1093 }
1094 };
1095
1096 /**
1097 * Handler for blocked popups, triggered by DOMUpdatePageReport events in browser.xml
1098 */
1099 var PopupBlockerObserver = {
1100 init: function init() {
1101 Elements.browsers.addEventListener("mousedown", this, true);
1102 },
1103
1104 handleEvent: function handleEvent(aEvent) {
1105 switch (aEvent.type) {
1106 case "mousedown":
1107 let box = Browser.getNotificationBox();
1108 let notification = box.getNotificationWithValue("popup-blocked");
1109 if (notification && !notification.contains(aEvent.target))
1110 box.removeNotification(notification);
1111 break;
1112 }
1113 },
1114
1115 onUpdatePageReport: function onUpdatePageReport(aEvent) {
1116 var cBrowser = Browser.selectedBrowser;
1117 if (aEvent.originalTarget != cBrowser)
1118 return;
1119
1120 if (!cBrowser.pageReport)
1121 return;
1122
1123 let result = Services.perms.testExactPermission(Browser.selectedBrowser.currentURI, "popup");
1124 if (result == Ci.nsIPermissionManager.DENY_ACTION)
1125 return;
1126
1127 // Only show the notification again if we've not already shown it. Since
1128 // notifications are per-browser, we don't need to worry about re-adding
1129 // it.
1130 if (!cBrowser.pageReport.reported) {
1131 if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) {
1132 var brandShortName = Strings.brand.GetStringFromName("brandShortName");
1133 var popupCount = cBrowser.pageReport.length;
1134
1135 let strings = Strings.browser;
1136 let message = PluralForm.get(popupCount, strings.GetStringFromName("popupWarning.message"))
1137 .replace("#1", brandShortName)
1138 .replace("#2", popupCount);
1139
1140 var notificationBox = Browser.getNotificationBox();
1141 var notification = notificationBox.getNotificationWithValue("popup-blocked");
1142 if (notification) {
1143 notification.label = message;
1144 }
1145 else {
1146 var buttons = [
1147 {
1148 isDefault: false,
1149 label: strings.GetStringFromName("popupButtonAllowOnce2"),
1150 accessKey: "",
1151 callback: function() { PopupBlockerObserver.showPopupsForSite(); }
1152 },
1153 {
1154 label: strings.GetStringFromName("popupButtonAlwaysAllow3"),
1155 accessKey: "",
1156 callback: function() { PopupBlockerObserver.allowPopupsForSite(true); }
1157 },
1158 {
1159 label: strings.GetStringFromName("popupButtonNeverWarn3"),
1160 accessKey: "",
1161 callback: function() { PopupBlockerObserver.allowPopupsForSite(false); }
1162 }
1163 ];
1164
1165 const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
1166 notificationBox.appendNotification(message, "popup-blocked",
1167 "chrome://browser/skin/images/infobar-popup.png",
1168 priority, buttons);
1169 }
1170 }
1171 // Record the fact that we've reported this blocked popup, so we don't
1172 // show it again.
1173 cBrowser.pageReport.reported = true;
1174 }
1175 },
1176
1177 allowPopupsForSite: function allowPopupsForSite(aAllow) {
1178 var currentURI = Browser.selectedBrowser.currentURI;
1179 Services.perms.add(currentURI, "popup", aAllow
1180 ? Ci.nsIPermissionManager.ALLOW_ACTION
1181 : Ci.nsIPermissionManager.DENY_ACTION);
1182
1183 Browser.getNotificationBox().removeCurrentNotification();
1184 },
1185
1186 showPopupsForSite: function showPopupsForSite() {
1187 let uri = Browser.selectedBrowser.currentURI;
1188 let pageReport = Browser.selectedBrowser.pageReport;
1189 if (pageReport) {
1190 for (let i = 0; i < pageReport.length; ++i) {
1191 var popupURIspec = pageReport[i].popupWindowURI.spec;
1192
1193 // Sometimes the popup URI that we get back from the pageReport
1194 // isn't useful (for instance, netscape.com's popup URI ends up
1195 // being "http://www.netscape.com", which isn't really the URI of
1196 // the popup they're trying to show). This isn't going to be
1197 // useful to the user, so we won't create a menu item for it.
1198 if (popupURIspec == "" || !Util.isURLMemorable(popupURIspec) || popupURIspec == uri.spec)
1199 continue;
1200
1201 let popupFeatures = pageReport[i].popupWindowFeatures;
1202 let popupName = pageReport[i].popupWindowName;
1203
1204 Browser.addTab(popupURIspec, false, Browser.selectedTab);
1205 }
1206 }
1207 }
1208 };
1209
1210 var SessionHistoryObserver = {
1211 observe: function sho_observe(aSubject, aTopic, aData) {
1212 if (aTopic != "browser:purge-session-history")
1213 return;
1214
1215 let newTab = Browser.addTab("about:start", true);
1216 let tab = Browser._tabs[0];
1217 while(tab != newTab) {
1218 Browser.closeTab(tab, { forceClose: true } );
1219 tab = Browser._tabs[0];
1220 }
1221
1222 PlacesUtils.history.removeAllPages();
1223
1224 // Clear undo history of the URL bar
1225 BrowserUI._edit.editor.transactionManager.clear();
1226 }
1227 };
1228
1229 function getNotificationBox(aBrowser) {
1230 return Browser.getNotificationBox(aBrowser);
1231 }
1232
1233 function showDownloadManager(aWindowContext, aID, aReason) {
1234 // TODO: Bug 883962: Toggle the downloads infobar as our current "download manager".
1235 }
1236
1237 function Tab(aURI, aParams, aOwner) {
1238 this._id = null;
1239 this._browser = null;
1240 this._notification = null;
1241 this._loading = false;
1242 this._progressActive = false;
1243 this._progressCount = 0;
1244 this._chromeTab = null;
1245 this._eventDeferred = null;
1246 this._updateThumbnailTimeout = null;
1247
1248 this._private = false;
1249 if ("private" in aParams) {
1250 this._private = aParams.private;
1251 } else if (aOwner) {
1252 this._private = aOwner._private;
1253 }
1254
1255 this.owner = aOwner || null;
1256
1257 // Set to 0 since new tabs that have not been viewed yet are good tabs to
1258 // toss if app needs more memory.
1259 this.lastSelected = 0;
1260
1261 // aParams is an object that contains some properties for the initial tab
1262 // loading like flags, a referrerURI, a charset or even a postData.
1263 this.create(aURI, aParams || {}, aOwner);
1264
1265 // default tabs to inactive (i.e. no display port)
1266 this.active = false;
1267 }
1268
1269 Tab.prototype = {
1270 get browser() {
1271 return this._browser;
1272 },
1273
1274 get notification() {
1275 return this._notification;
1276 },
1277
1278 get chromeTab() {
1279 return this._chromeTab;
1280 },
1281
1282 get isPrivate() {
1283 return this._private;
1284 },
1285
1286 get pageShowPromise() {
1287 return this._eventDeferred ? this._eventDeferred.promise : null;
1288 },
1289
1290 startLoading: function startLoading() {
1291 if (this._loading) {
1292 let stack = new Error().stack;
1293 throw "Already Loading!\n" + stack;
1294 }
1295 this._loading = true;
1296 },
1297
1298 endLoading: function endLoading() {
1299 this._loading = false;
1300 this.updateFavicon();
1301 },
1302
1303 isLoading: function isLoading() {
1304 return this._loading;
1305 },
1306
1307 create: function create(aURI, aParams, aOwner) {
1308 this._eventDeferred = Promise.defer();
1309
1310 this._chromeTab = Elements.tabList.addTab(aParams.index);
1311 if (this.isPrivate) {
1312 this._chromeTab.setAttribute("private", "true");
1313 }
1314
1315 this._id = Browser.createTabId();
1316 let browser = this._createBrowser(aURI, null);
1317
1318 let self = this;
1319 function onPageShowEvent(aEvent) {
1320 browser.removeEventListener("pageshow", onPageShowEvent);
1321 if (self._eventDeferred) {
1322 self._eventDeferred.resolve(self);
1323 }
1324 self._eventDeferred = null;
1325 }
1326 browser.addEventListener("pageshow", onPageShowEvent, true);
1327 browser.addEventListener("DOMWindowCreated", this, false);
1328 browser.addEventListener("StartUIChange", this, false);
1329 Elements.browsers.addEventListener("SizeChanged", this, false);
1330
1331 browser.messageManager.addMessageListener("Content:StateChange", this);
1332
1333 if (aOwner)
1334 this._copyHistoryFrom(aOwner);
1335 this._loadUsingParams(browser, aURI, aParams);
1336 },
1337
1338 updateViewport: function (aEvent) {
1339 // <meta name=viewport> is not yet supported; just use the browser size.
1340 let browser = this.browser;
1341
1342 // On the start page we add padding to keep the browser above the navbar.
1343 let paddingBottom = parseInt(getComputedStyle(browser).paddingBottom, 10);
1344 let height = browser.clientHeight - paddingBottom;
1345
1346 browser.setWindowSize(browser.clientWidth, height);
1347 },
1348
1349 handleEvent: function (aEvent) {
1350 switch (aEvent.type) {
1351 case "DOMWindowCreated":
1352 case "StartUIChange":
1353 this.updateViewport();
1354 break;
1355 case "SizeChanged":
1356 this.updateViewport();
1357 this._delayUpdateThumbnail();
1358 break;
1359 case "AlertClose": {
1360 if (this == Browser.selectedTab) {
1361 this.updateViewport();
1362 }
1363 break;
1364 }
1365 }
1366 },
1367
1368 receiveMessage: function(aMessage) {
1369 switch (aMessage.name) {
1370 case "Content:StateChange":
1371 // update the thumbnail now...
1372 this.updateThumbnail();
1373 // ...and in a little while to capture page after load.
1374 if (aMessage.json.stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
1375 this._delayUpdateThumbnail();
1376 }
1377 break;
1378 }
1379 },
1380
1381 _delayUpdateThumbnail: function() {
1382 clearTimeout(this._updateThumbnailTimeout);
1383 this._updateThumbnailTimeout = setTimeout(() => {
1384 this.updateThumbnail();
1385 }, kTabThumbnailDelayCapture);
1386 },
1387
1388 destroy: function destroy() {
1389 this._browser.messageManager.removeMessageListener("Content:StateChange", this);
1390 this._browser.removeEventListener("DOMWindowCreated", this, false);
1391 this._browser.removeEventListener("StartUIChange", this, false);
1392 Elements.browsers.removeEventListener("SizeChanged", this, false);
1393 clearTimeout(this._updateThumbnailTimeout);
1394
1395 Elements.tabList.removeTab(this._chromeTab);
1396 this._chromeTab = null;
1397 this._destroyBrowser();
1398 },
1399
1400 resurrect: function resurrect() {
1401 let dead = this._browser;
1402 let active = this.active;
1403
1404 // Hold onto the session store data
1405 let session = { data: dead.__SS_data, extra: dead.__SS_extdata };
1406
1407 // We need this data to correctly create and position the new browser
1408 // If this browser is already a zombie, fallback to the session data
1409 let currentURL = dead.__SS_restore ? session.data.entries[0].url : dead.currentURI.spec;
1410 let sibling = dead.nextSibling;
1411
1412 // Destory and re-create the browser
1413 this._destroyBrowser();
1414 let browser = this._createBrowser(currentURL, sibling);
1415 if (active)
1416 this.active = true;
1417
1418 // Reattach session store data and flag this browser so it is restored on select
1419 browser.__SS_data = session.data;
1420 browser.__SS_extdata = session.extra;
1421 browser.__SS_restore = true;
1422 },
1423
1424 _copyHistoryFrom: function _copyHistoryFrom(tab) {
1425 let otherHistory = tab._browser._webNavigation.sessionHistory;
1426 let history = this._browser._webNavigation.sessionHistory;
1427
1428 // Ensure that history is initialized
1429 history.QueryInterface(Ci.nsISHistoryInternal);
1430
1431 for (let i = 0, length = otherHistory.index; i <= length; i++)
1432 history.addEntry(otherHistory.getEntryAtIndex(i, false), true);
1433 },
1434
1435 _loadUsingParams: function _loadUsingParams(aBrowser, aURI, aParams) {
1436 let flags = aParams.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
1437 let postData = ("postData" in aParams && aParams.postData) ? aParams.postData.value : null;
1438 let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
1439 let charset = "charset" in aParams ? aParams.charset : null;
1440 aBrowser.loadURIWithFlags(aURI, flags, referrerURI, charset, postData);
1441 },
1442
1443 _createBrowser: function _createBrowser(aURI, aInsertBefore) {
1444 if (this._browser)
1445 throw "Browser already exists";
1446
1447 // Create a notification box around the browser. Note this includes
1448 // the input overlay we use to shade content from input events when
1449 // we're intercepting touch input.
1450 let notification = this._notification = document.createElement("notificationbox");
1451
1452 let browser = this._browser = document.createElement("browser");
1453 browser.id = "browser-" + this._id;
1454 this._chromeTab.linkedBrowser = browser;
1455
1456 browser.setAttribute("type", "content-targetable");
1457
1458 let useRemote = Services.appinfo.browserTabsRemote;
1459 let useLocal = Util.isLocalScheme(aURI);
1460 browser.setAttribute("remote", (!useLocal && useRemote) ? "true" : "false");
1461
1462 // Append the browser to the document, which should start the page load
1463 let stack = document.createElementNS(XUL_NS, "stack");
1464 stack.className = "browserStack";
1465 stack.appendChild(browser);
1466 stack.setAttribute("flex", "1");
1467 notification.appendChild(stack);
1468 Elements.browsers.insertBefore(notification, aInsertBefore);
1469
1470 notification.dir = "reverse";
1471 notification.addEventListener("AlertClose", this);
1472
1473 // let the content area manager know about this browser.
1474 ContentAreaObserver.onBrowserCreated(browser);
1475
1476 if (this.isPrivate) {
1477 let ctx = browser.docShell.QueryInterface(Ci.nsILoadContext);
1478 ctx.usePrivateBrowsing = true;
1479 }
1480
1481 // stop about:blank from loading
1482 browser.stop();
1483
1484 let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
1485 fl.renderMode = Ci.nsIFrameLoader.RENDER_MODE_ASYNC_SCROLL;
1486
1487 return browser;
1488 },
1489
1490 _destroyBrowser: function _destroyBrowser() {
1491 if (this._browser) {
1492 let notification = this._notification;
1493 notification.removeEventListener("AlertClose", this);
1494 let browser = this._browser;
1495 browser.active = false;
1496
1497 this._notification = null;
1498 this._browser = null;
1499 this._loading = false;
1500
1501 Elements.browsers.removeChild(notification);
1502 }
1503 },
1504
1505 updateThumbnail: function updateThumbnail() {
1506 if (!this.isPrivate) {
1507 PageThumbs.captureToCanvas(this.browser.contentWindow, this._chromeTab.thumbnailCanvas);
1508 }
1509 },
1510
1511 updateFavicon: function updateFavicon() {
1512 this._chromeTab.updateFavicon(this._browser.mIconURL);
1513 },
1514
1515 set active(aActive) {
1516 if (!this._browser)
1517 return;
1518
1519 let notification = this._notification;
1520 let browser = this._browser;
1521
1522 if (aActive) {
1523 notification.classList.add("active-tab-notificationbox");
1524 browser.setAttribute("type", "content-primary");
1525 Elements.browsers.selectedPanel = notification;
1526 browser.active = true;
1527 Elements.tabList.selectedTab = this._chromeTab;
1528 browser.focus();
1529 } else {
1530 notification.classList.remove("active-tab-notificationbox");
1531 browser.messageManager.sendAsyncMessage("Browser:Blur", { });
1532 browser.setAttribute("type", "content-targetable");
1533 browser.active = false;
1534 }
1535 },
1536
1537 get active() {
1538 if (!this._browser)
1539 return false;
1540 return this._browser.getAttribute("type") == "content-primary";
1541 },
1542
1543 toString: function() {
1544 return "[Tab " + (this._browser ? this._browser.currentURI.spec : "(no browser)") + "]";
1545 }
1546 };
1547
1548 // Helper used to hide IPC / non-IPC differences for rendering to a canvas
1549 function rendererFactory(aBrowser, aCanvas) {
1550 let wrapper = {};
1551
1552 if (aBrowser.contentWindow) {
1553 let ctx = aCanvas.getContext("2d");
1554 let draw = function(browser, aLeft, aTop, aWidth, aHeight, aColor, aFlags) {
1555 ctx.drawWindow(browser.contentWindow, aLeft, aTop, aWidth, aHeight, aColor, aFlags);
1556 let e = document.createEvent("HTMLEvents");
1557 e.initEvent("MozAsyncCanvasRender", true, true);
1558 aCanvas.dispatchEvent(e);
1559 };
1560 wrapper.checkBrowser = function(browser) {
1561 return browser.contentWindow;
1562 };
1563 wrapper.drawContent = function(callback) {
1564 callback(ctx, draw);
1565 };
1566 }
1567 else {
1568 let ctx = aCanvas.MozGetIPCContext("2d");
1569 let draw = function(browser, aLeft, aTop, aWidth, aHeight, aColor, aFlags) {
1570 ctx.asyncDrawXULElement(browser, aLeft, aTop, aWidth, aHeight, aColor, aFlags);
1571 };
1572 wrapper.checkBrowser = function(browser) {
1573 return !browser.contentWindow;
1574 };
1575 wrapper.drawContent = function(callback) {
1576 callback(ctx, draw);
1577 };
1578 }
1579
1580 return wrapper;
1581 };
1582
1583 // Based on ClickEventHandler from /browser/base/content/content.js
1584 let ClickEventHandler = {
1585 init: function () {
1586 gEventListenerService.addSystemEventListener(Elements.browsers, "click", this, true);
1587 },
1588
1589 uninit: function () {
1590 gEventListenerService.removeSystemEventListener(Elements.browsers, "click", this, true);
1591 },
1592
1593 handleEvent: function (aEvent) {
1594 if (!aEvent.isTrusted || aEvent.defaultPrevented) {
1595 return;
1596 }
1597 let [href, node] = this._hrefAndLinkNodeForClickEvent(aEvent);
1598 if (href && (aEvent.button == 1 || aEvent.ctrlKey)) {
1599 // Open link in a new tab for middle-click or ctrl-click
1600 BrowserUI.openLinkInNewTab(href, aEvent.shiftKey, Browser.selectedTab);
1601 }
1602 },
1603
1604 /**
1605 * Extracts linkNode and href for the current click target.
1606 *
1607 * @param event
1608 * The click event.
1609 * @return [href, linkNode].
1610 *
1611 * @note linkNode will be null if the click wasn't on an anchor
1612 * element (or XLink).
1613 */
1614 _hrefAndLinkNodeForClickEvent: function(event) {
1615 function isHTMLLink(aNode) {
1616 return ((aNode instanceof content.HTMLAnchorElement && aNode.href) ||
1617 (aNode instanceof content.HTMLAreaElement && aNode.href) ||
1618 aNode instanceof content.HTMLLinkElement);
1619 }
1620
1621 let node = event.target;
1622 while (node && !isHTMLLink(node)) {
1623 node = node.parentNode;
1624 }
1625
1626 if (node)
1627 return [node.href, node];
1628
1629 // If there is no linkNode, try simple XLink.
1630 let href, baseURI;
1631 node = event.target;
1632 while (node && !href) {
1633 if (node.nodeType == content.Node.ELEMENT_NODE) {
1634 href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
1635 if (href)
1636 baseURI = node.ownerDocument.baseURIObject;
1637 }
1638 node = node.parentNode;
1639 }
1640
1641 // In case of XLink, we don't return the node we got href from since
1642 // callers expect <a>-like elements.
1643 // Note: makeURI() will throw if aUri is not a valid URI.
1644 return [href ? Services.io.newURI(href, null, baseURI).spec : null, null];
1645 }
1646 };

mercurial