зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1223573 - Part 3. Move browser-loop.js to begin forming bootstrap.js. r=mikedeboer
This commit is contained in:
Родитель
8f667d31b4
Коммит
e3d52002a4
|
@ -1,613 +0,0 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// the "exported" symbols
|
||||
var LoopUI;
|
||||
|
||||
(function() {
|
||||
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
const kBrowserSharingNotificationId = "loop-sharing-notification";
|
||||
const kPrefBrowserSharingInfoBar = "browserSharing.showInfoBar";
|
||||
|
||||
LoopUI = {
|
||||
/**
|
||||
* @var {XULWidgetSingleWrapper} toolbarButton Getter for the Loop toolbarbutton
|
||||
* instance for this window.
|
||||
*/
|
||||
get toolbarButton() {
|
||||
delete this.toolbarButton;
|
||||
return this.toolbarButton = CustomizableUI.getWidget("loop-button").forWindow(window);
|
||||
},
|
||||
|
||||
/**
|
||||
* @var {XULElement} panel Getter for the Loop panel element.
|
||||
*/
|
||||
get panel() {
|
||||
delete this.panel;
|
||||
return this.panel = document.getElementById("loop-notification-panel");
|
||||
},
|
||||
|
||||
/**
|
||||
* @var {XULElement|null} browser Getter for the Loop panel browser element.
|
||||
* Will be NULL if the panel hasn't loaded yet.
|
||||
*/
|
||||
get browser() {
|
||||
let browser = document.querySelector("#loop-notification-panel > #loop-panel-iframe");
|
||||
if (browser) {
|
||||
delete this.browser;
|
||||
this.browser = browser;
|
||||
}
|
||||
return browser;
|
||||
},
|
||||
|
||||
/**
|
||||
* @var {String|null} selectedTab Getter for the name of the currently selected
|
||||
* tab inside the Loop panel. Will be NULL if
|
||||
* the panel hasn't loaded yet.
|
||||
*/
|
||||
get selectedTab() {
|
||||
if (!this.browser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let selectedTab = this.browser.contentDocument.querySelector(".tab-view > .selected");
|
||||
return selectedTab && selectedTab.getAttribute("data-tab-name");
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {Promise}
|
||||
*/
|
||||
promiseDocumentVisible(aDocument) {
|
||||
if (!aDocument.hidden) {
|
||||
return Promise.resolve(aDocument);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
aDocument.addEventListener("visibilitychange", function onVisibilityChanged() {
|
||||
aDocument.removeEventListener("visibilitychange", onVisibilityChanged);
|
||||
resolve(aDocument);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle between opening or hiding the Loop panel.
|
||||
*
|
||||
* @param {DOMEvent} [event] Optional event that triggered the call to this
|
||||
* function.
|
||||
* @param {String} [tabId] Optional name of the tab to select after the panel
|
||||
* has opened. Does nothing when the panel is hidden.
|
||||
* @return {Promise}
|
||||
*/
|
||||
togglePanel: function(event, tabId = null) {
|
||||
if (!this.panel) {
|
||||
// We're on the hidden window! What fun!
|
||||
let obs = win => {
|
||||
Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
|
||||
win.LoopUI.togglePanel(event, tabId);
|
||||
};
|
||||
Services.obs.addObserver(obs, "browser-delayed-startup-finished", false);
|
||||
return OpenBrowserWindow();
|
||||
}
|
||||
if (this.panel.state == "open") {
|
||||
return new Promise(resolve => {
|
||||
this.panel.hidePopup();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
return this.openCallPanel(event, tabId).then(doc => {
|
||||
let fm = Services.focus;
|
||||
fm.moveFocus(doc.defaultView, null, fm.MOVEFOCUS_FIRST, fm.FLAG_NOSCROLL);
|
||||
}).catch(err => {
|
||||
Cu.reportError(x);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the panel for Loop and sizes it appropriately.
|
||||
*
|
||||
* @param {event} event The event opening the panel, used to anchor
|
||||
* the panel to the button which triggers it.
|
||||
* @param {String} [tabId] Identifier of the tab to select when the panel is
|
||||
* opened. Example: 'rooms', 'contacts', etc.
|
||||
* @return {Promise}
|
||||
*/
|
||||
openCallPanel: function(event, tabId = null) {
|
||||
return new Promise((resolve) => {
|
||||
let callback = iframe => {
|
||||
// Helper function to show a specific tab view in the panel.
|
||||
function showTab() {
|
||||
if (!tabId) {
|
||||
resolve(LoopUI.promiseDocumentVisible(iframe.contentDocument));
|
||||
return;
|
||||
}
|
||||
|
||||
let win = iframe.contentWindow;
|
||||
let ev = new win.CustomEvent("UIAction", Cu.cloneInto({
|
||||
detail: {
|
||||
action: "selectTab",
|
||||
tab: tabId
|
||||
}
|
||||
}, win));
|
||||
win.dispatchEvent(ev);
|
||||
resolve(LoopUI.promiseDocumentVisible(iframe.contentDocument));
|
||||
}
|
||||
|
||||
// If the panel has been opened and initialized before, we can skip waiting
|
||||
// for the content to load - because it's already there.
|
||||
if (("contentWindow" in iframe) && iframe.contentWindow.document.readyState == "complete") {
|
||||
showTab();
|
||||
return;
|
||||
}
|
||||
|
||||
let documentDOMLoaded = () => {
|
||||
iframe.removeEventListener("DOMContentLoaded", documentDOMLoaded, true);
|
||||
// Handle window.close correctly on the panel.
|
||||
this.hookWindowCloseForPanelClose(iframe.contentWindow);
|
||||
iframe.contentWindow.addEventListener("loopPanelInitialized", function loopPanelInitialized() {
|
||||
iframe.contentWindow.removeEventListener("loopPanelInitialized",
|
||||
loopPanelInitialized);
|
||||
showTab();
|
||||
});
|
||||
};
|
||||
iframe.addEventListener("DOMContentLoaded", documentDOMLoaded, true);
|
||||
};
|
||||
|
||||
// Used to clear the temporary "login" state from the button.
|
||||
Services.obs.notifyObservers(null, "loop-status-changed", null);
|
||||
|
||||
this.shouldResumeTour().then((resume) => {
|
||||
if (resume) {
|
||||
// Assume the conversation with the visitor wasn't open since we would
|
||||
// have resumed the tour as soon as the visitor joined if it was (and
|
||||
// the pref would have been set to false already.
|
||||
this.MozLoopService.resumeTour("waiting");
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.LoopAPI.initialize();
|
||||
|
||||
let anchor = event ? event.target : this.toolbarButton.anchor;
|
||||
let setHeight = 410;
|
||||
if (gBrowser.selectedBrowser.getAttribute("remote") === "true") {
|
||||
setHeight = 262;
|
||||
}
|
||||
this.PanelFrame.showPopup(window, anchor,
|
||||
"loop", null, "about:looppanel",
|
||||
// Loop wants a fixed size for the panel. This also stops it dynamically resizing.
|
||||
{ width: 330, height: setHeight },
|
||||
callback);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Method to know whether actions to open the panel should instead resume the tour.
|
||||
*
|
||||
* We need the panel to be opened via UITour so that it gets @noautohide.
|
||||
*
|
||||
* @return {Promise} resolving with a {Boolean} of whether the tour should be resumed instead of
|
||||
* opening the panel.
|
||||
*/
|
||||
shouldResumeTour: Task.async(function* () {
|
||||
// Resume the FTU tour if this is the first time a room was joined by
|
||||
// someone else since the tour.
|
||||
if (!Services.prefs.getBoolPref("loop.gettingStarted.resumeOnFirstJoin")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.LoopRooms.participantsCount) {
|
||||
// Nobody is in the rooms
|
||||
return false;
|
||||
}
|
||||
|
||||
let roomsWithNonOwners = yield this.roomsWithNonOwners();
|
||||
if (!roomsWithNonOwners.length) {
|
||||
// We were the only one in a room but we want to know about someone else joining.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
/**
|
||||
* @return {Promise} resolved with an array of Rooms with participants (excluding owners)
|
||||
*/
|
||||
roomsWithNonOwners: function() {
|
||||
return new Promise(resolve => {
|
||||
this.LoopRooms.getAll((error, rooms) => {
|
||||
let roomsWithNonOwners = [];
|
||||
for (let room of rooms) {
|
||||
if (!("participants" in room)) {
|
||||
continue;
|
||||
}
|
||||
let numNonOwners = room.participants.filter(participant => !participant.owner).length;
|
||||
if (!numNonOwners) {
|
||||
continue;
|
||||
}
|
||||
roomsWithNonOwners.push(room);
|
||||
}
|
||||
resolve(roomsWithNonOwners);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggers the initialization of the loop service. Called by
|
||||
* delayedStartup.
|
||||
*/
|
||||
init: function() {
|
||||
// Add observer notifications before the service is initialized
|
||||
Services.obs.addObserver(this, "loop-status-changed", false);
|
||||
|
||||
// This is a promise for test purposes, but we don't want to be logging
|
||||
// expected errors to the console, so we catch them here.
|
||||
this.MozLoopService.initialize().catch(ex => {
|
||||
if (!ex.message ||
|
||||
(!ex.message.contains("not enabled") &&
|
||||
!ex.message.contains("not needed"))) {
|
||||
console.error(ex);
|
||||
}
|
||||
});
|
||||
this.updateToolbarState();
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
Services.obs.removeObserver(this, "loop-status-changed");
|
||||
},
|
||||
|
||||
// Implements nsIObserver
|
||||
observe: function(subject, topic, data) {
|
||||
if (topic != "loop-status-changed") {
|
||||
return;
|
||||
}
|
||||
this.updateToolbarState(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the toolbar/menu-button state to reflect Loop status.
|
||||
*
|
||||
* @param {string} [aReason] Some states are only shown if
|
||||
* a related reason is provided.
|
||||
*
|
||||
* aReason="login": Used after a login is completed
|
||||
* successfully. This is used so the state can be
|
||||
* temporarily shown until the next state change.
|
||||
*/
|
||||
updateToolbarState: function(aReason = null) {
|
||||
if (!this.toolbarButton.node) {
|
||||
return;
|
||||
}
|
||||
let state = "";
|
||||
let mozL10nId = "loop-call-button3";
|
||||
let suffix = ".tooltiptext";
|
||||
if (this.MozLoopService.errors.size) {
|
||||
state = "error";
|
||||
mozL10nId += "-error";
|
||||
} else if (this.MozLoopService.screenShareActive) {
|
||||
state = "action";
|
||||
mozL10nId += "-screensharing";
|
||||
} else if (aReason == "login" && this.MozLoopService.userProfile) {
|
||||
state = "active";
|
||||
mozL10nId += "-active";
|
||||
suffix += "2";
|
||||
} else if (this.MozLoopService.doNotDisturb) {
|
||||
state = "disabled";
|
||||
mozL10nId += "-donotdisturb";
|
||||
} else if (this.MozLoopService.roomsParticipantsCount > 0) {
|
||||
state = "active";
|
||||
this.roomsWithNonOwners().then(roomsWithNonOwners => {
|
||||
if (roomsWithNonOwners.length > 0) {
|
||||
mozL10nId += "-participantswaiting";
|
||||
} else {
|
||||
mozL10nId += "-active";
|
||||
}
|
||||
|
||||
suffix += "2";
|
||||
this.updateTooltiptext(mozL10nId + suffix);
|
||||
this.toolbarButton.node.setAttribute("state", state);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
suffix += "2";
|
||||
}
|
||||
|
||||
this.toolbarButton.node.setAttribute("state", state);
|
||||
this.updateTooltiptext(mozL10nId + suffix);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the tootltiptext to reflect Loop status.
|
||||
*
|
||||
* @param {string} [mozL10nId] l10n ID that refelct the current
|
||||
* Loop status.
|
||||
*/
|
||||
updateTooltiptext: function(mozL10nId) {
|
||||
this.toolbarButton.node.setAttribute("tooltiptext", mozL10nId);
|
||||
var tooltiptext = CustomizableUI.getLocalizedProperty(this.toolbarButton, "tooltiptext");
|
||||
this.toolbarButton.node.setAttribute("tooltiptext", tooltiptext);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a desktop notification when 'do not disturb' isn't enabled.
|
||||
*
|
||||
* @param {Object} options Set of options that may tweak the appearance and
|
||||
* behavior of the notification.
|
||||
* Option params:
|
||||
* - {String} title Notification title message
|
||||
* - {String} [message] Notification body text
|
||||
* - {String} [icon] Notification icon
|
||||
* - {String} [sound] Sound to play
|
||||
* - {String} [selectTab] Tab to select when the panel
|
||||
* opens
|
||||
* - {Function} [onclick] Callback to invoke when
|
||||
* the notification is clicked.
|
||||
* Opens the panel by default.
|
||||
*/
|
||||
showNotification: function(options) {
|
||||
if (this.MozLoopService.doNotDisturb) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.title) {
|
||||
throw new Error("Missing title, can not display notification");
|
||||
}
|
||||
|
||||
let notificationOptions = {
|
||||
body: options.message || ""
|
||||
};
|
||||
if (options.icon) {
|
||||
notificationOptions.icon = options.icon;
|
||||
}
|
||||
if (options.sound) {
|
||||
// This will not do anything, until bug bug 1105222 is resolved.
|
||||
notificationOptions.mozbehavior = {
|
||||
soundFile: ""
|
||||
};
|
||||
this.playSound(options.sound);
|
||||
}
|
||||
|
||||
let notification = new window.Notification(options.title, notificationOptions);
|
||||
notification.addEventListener("click", e => {
|
||||
if (window.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.focus();
|
||||
} catch (ex) {}
|
||||
|
||||
// We need a setTimeout here, otherwise the panel won't show after the
|
||||
// window received focus.
|
||||
window.setTimeout(() => {
|
||||
if (typeof options.onclick == "function") {
|
||||
options.onclick();
|
||||
} else {
|
||||
// Open the Loop panel as a default action.
|
||||
this.openCallPanel(null, options.selectTab || null);
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Play a sound in this window IF there's no sound playing yet.
|
||||
*
|
||||
* @param {String} name Name of the sound, like 'ringtone' or 'room-joined'
|
||||
*/
|
||||
playSound: function(name) {
|
||||
if (this.ActiveSound || this.MozLoopService.doNotDisturb) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeSound = new window.Audio();
|
||||
this.activeSound.src = `chrome://browser/content/loop/shared/sounds/${name}.ogg`;
|
||||
this.activeSound.load();
|
||||
this.activeSound.play();
|
||||
|
||||
this.activeSound.addEventListener("ended", () => this.activeSound = undefined, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start listening to selected tab changes and notify any content page that's
|
||||
* listening to 'BrowserSwitch' push messages.
|
||||
*
|
||||
* Push message parameters:
|
||||
* - {Integer} windowId The new windowId for the browser.
|
||||
*/
|
||||
startBrowserSharing: function() {
|
||||
if (!this._listeningToTabSelect) {
|
||||
gBrowser.tabContainer.addEventListener("TabSelect", this);
|
||||
this._listeningToTabSelect = true;
|
||||
}
|
||||
|
||||
this._maybeShowBrowserSharingInfoBar();
|
||||
|
||||
// Get the first window Id for the listener.
|
||||
this.LoopAPI.broadcastPushMessage("BrowserSwitch",
|
||||
gBrowser.selectedBrowser.outerWindowID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop listening to selected tab changes.
|
||||
*/
|
||||
stopBrowserSharing: function() {
|
||||
if (!this._listeningToTabSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._hideBrowserSharingInfoBar();
|
||||
gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
||||
this._listeningToTabSelect = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to fetch a localized string via the MozLoopService API.
|
||||
* It's currently inconveniently wrapped inside a string of stringified JSON.
|
||||
*
|
||||
* @param {String} key The element id to get strings for.
|
||||
* @return {String}
|
||||
*/
|
||||
_getString: function(key) {
|
||||
let str = this.MozLoopService.getStrings(key);
|
||||
if (str) {
|
||||
str = JSON.parse(str).textContent;
|
||||
}
|
||||
return str;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows an infobar notification at the top of the browser window that warns
|
||||
* the user that their browser tabs are being broadcasted through the current
|
||||
* conversation.
|
||||
*/
|
||||
_maybeShowBrowserSharingInfoBar: function() {
|
||||
this._hideBrowserSharingInfoBar();
|
||||
|
||||
// Don't show the infobar if it's been permanently disabled from the menu.
|
||||
if (!this.MozLoopService.getLoopPref(kPrefBrowserSharingInfoBar)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let box = gBrowser.getNotificationBox();
|
||||
let paused = false;
|
||||
let bar = box.appendNotification(
|
||||
this._getString("infobar_screenshare_browser_message"),
|
||||
kBrowserSharingNotificationId,
|
||||
// Icon is defined in browser theme CSS.
|
||||
null,
|
||||
box.PRIORITY_WARNING_LOW,
|
||||
[{
|
||||
label: this._getString("infobar_button_pause_label"),
|
||||
accessKey: this._getString("infobar_button_pause_accesskey"),
|
||||
isDefault: false,
|
||||
callback: (event, buttonInfo, buttonNode) => {
|
||||
paused = !paused;
|
||||
bar.label = paused ? this._getString("infobar_screenshare_paused_browser_message") :
|
||||
this._getString("infobar_screenshare_browser_message");
|
||||
bar.classList.toggle("paused", paused);
|
||||
buttonNode.label = paused ? this._getString("infobar_button_resume_label") :
|
||||
this._getString("infobar_button_pause_label");
|
||||
buttonNode.accessKey = paused ? this._getString("infobar_button_resume_accesskey") :
|
||||
this._getString("infobar_button_pause_accesskey");
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: this._getString("infobar_button_stop_label"),
|
||||
accessKey: this._getString("infobar_button_stop_accesskey"),
|
||||
isDefault: true,
|
||||
callback: () => {
|
||||
this._hideBrowserSharingInfoBar();
|
||||
LoopUI.MozLoopService.hangupAllChatWindows();
|
||||
}
|
||||
}]
|
||||
);
|
||||
|
||||
// Keep showing the notification bar until the user explicitly closes it.
|
||||
bar.persistence = -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides the infobar, permanantly if requested.
|
||||
*
|
||||
* @param {Boolean} permanently Flag that determines if the infobar will never
|
||||
* been shown again. Defaults to `false`.
|
||||
* @return {Boolean} |true| if the infobar was hidden here.
|
||||
*/
|
||||
_hideBrowserSharingInfoBar: function(permanently = false, browser) {
|
||||
browser = browser || gBrowser.selectedBrowser;
|
||||
let box = gBrowser.getNotificationBox(browser);
|
||||
let notification = box.getNotificationWithValue(kBrowserSharingNotificationId);
|
||||
let removed = false;
|
||||
if (notification) {
|
||||
box.removeNotification(notification);
|
||||
removed = true;
|
||||
}
|
||||
|
||||
if (permanently) {
|
||||
this.MozLoopService.setLoopPref(kPrefBrowserSharingInfoBar, false);
|
||||
}
|
||||
|
||||
return removed;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles events from gBrowser.
|
||||
*/
|
||||
handleEvent: function(event) {
|
||||
// We only should get "select" events.
|
||||
if (event.type != "TabSelect") {
|
||||
return;
|
||||
}
|
||||
|
||||
let wasVisible = false;
|
||||
// Hide the infobar from the previous tab.
|
||||
if (event.detail.previousTab) {
|
||||
wasVisible = this._hideBrowserSharingInfoBar(false,
|
||||
event.detail.previousTab.linkedBrowser);
|
||||
}
|
||||
|
||||
// We've changed the tab, so get the new window id.
|
||||
this.LoopAPI.broadcastPushMessage("BrowserSwitch",
|
||||
gBrowser.selectedBrowser.outerWindowID);
|
||||
|
||||
if (wasVisible) {
|
||||
// If the infobar was visible before, we should show it again after the
|
||||
// switch.
|
||||
this._maybeShowBrowserSharingInfoBar();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the favicon of the currently selected tab in the format of a data-uri.
|
||||
*
|
||||
* @param {Function} callback Function to be invoked with an error object as
|
||||
* its first argument when an error occurred or
|
||||
* a string as second argument when the favicon
|
||||
* has been fetched.
|
||||
*/
|
||||
getFavicon: function(callback) {
|
||||
let pageURI = gBrowser.selectedTab.linkedBrowser.currentURI.spec;
|
||||
// If the tab page’s url starts with http(s), fetch icon.
|
||||
if (!/^https?:/.test(pageURI)) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
this.PlacesUtils.promiseFaviconLinkUrl(pageURI).then(uri => {
|
||||
// We XHR the favicon to get a File object, which we can pass to the FileReader
|
||||
// object. The FileReader turns the File object into a data-uri.
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("get", uri.spec, true);
|
||||
xhr.responseType = "blob";
|
||||
xhr.overrideMimeType("image/x-icon");
|
||||
xhr.onload = () => {
|
||||
if (xhr.status != 200) {
|
||||
callback(new Error("Invalid status code received for favicon XHR: " + xhr.status));
|
||||
return;
|
||||
}
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.onload = reader.onload = () => callback(null, reader.result);
|
||||
reader.onerror = callback;
|
||||
reader.readAsDataURL(xhr.response);
|
||||
};
|
||||
xhr.onerror = callback;
|
||||
xhr.send();
|
||||
}).catch(err => {
|
||||
callback(err || new Error("No favicon found"));
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "hookWindowCloseForPanelClose", "resource://gre/modules/MozSocialAPI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "LoopAPI", "resource:///modules/loop/MozLoopAPI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "LoopRooms", "resource:///modules/loop/LoopRooms.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "MozLoopService", "resource:///modules/loop/MozLoopService.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "PanelFrame", "resource:///modules/PanelFrame.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
|
|
@ -274,7 +274,6 @@ var gInitialPages = [
|
|||
#include browser-fullScreen.js
|
||||
#include browser-fullZoom.js
|
||||
#include browser-gestureSupport.js
|
||||
#include browser-loop.js
|
||||
#include browser-places.js
|
||||
#include browser-plugins.js
|
||||
#include browser-safebrowsing.js
|
||||
|
@ -1357,8 +1356,6 @@ var gBrowserInit = {
|
|||
gDataNotificationInfoBar.init();
|
||||
#endif
|
||||
|
||||
LoopUI.init();
|
||||
|
||||
gBrowserThumbnails.init();
|
||||
|
||||
// Add Devtools menuitems and listeners
|
||||
|
@ -1540,7 +1537,6 @@ var gBrowserInit = {
|
|||
TabView.uninit();
|
||||
SocialUI.uninit();
|
||||
gBrowserThumbnails.uninit();
|
||||
LoopUI.uninit();
|
||||
FullZoom.destroy();
|
||||
|
||||
Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
|
||||
|
|
|
@ -0,0 +1,619 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var WindowListener = {
|
||||
|
||||
setupBrowserUI: function(window) {
|
||||
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
const kBrowserSharingNotificationId = "loop-sharing-notification";
|
||||
const kPrefBrowserSharingInfoBar = "browserSharing.showInfoBar";
|
||||
|
||||
let document = window.document;
|
||||
let gBrowser = window.gBrowser;
|
||||
let xhrClass = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
|
||||
let FileReader = window.FileReader;
|
||||
|
||||
// the "exported" symbols
|
||||
var LoopUI = {
|
||||
/**
|
||||
* @var {XULWidgetSingleWrapper} toolbarButton Getter for the Loop toolbarbutton
|
||||
* instance for this window.
|
||||
*/
|
||||
get toolbarButton() {
|
||||
delete this.toolbarButton;
|
||||
return this.toolbarButton = CustomizableUI.getWidget("loop-button").forWindow(window);
|
||||
},
|
||||
|
||||
/**
|
||||
* @var {XULElement} panel Getter for the Loop panel element.
|
||||
*/
|
||||
get panel() {
|
||||
delete this.panel;
|
||||
return this.panel = document.getElementById("loop-notification-panel");
|
||||
},
|
||||
|
||||
/**
|
||||
* @var {XULElement|null} browser Getter for the Loop panel browser element.
|
||||
* Will be NULL if the panel hasn't loaded yet.
|
||||
*/
|
||||
get browser() {
|
||||
let browser = document.querySelector("#loop-notification-panel > #loop-panel-iframe");
|
||||
if (browser) {
|
||||
delete this.browser;
|
||||
this.browser = browser;
|
||||
}
|
||||
return browser;
|
||||
},
|
||||
|
||||
/**
|
||||
* @var {String|null} selectedTab Getter for the name of the currently selected
|
||||
* tab inside the Loop panel. Will be NULL if
|
||||
* the panel hasn't loaded yet.
|
||||
*/
|
||||
get selectedTab() {
|
||||
if (!this.browser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let selectedTab = this.browser.contentDocument.querySelector(".tab-view > .selected");
|
||||
return selectedTab && selectedTab.getAttribute("data-tab-name");
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {Promise}
|
||||
*/
|
||||
promiseDocumentVisible(aDocument) {
|
||||
if (!aDocument.hidden) {
|
||||
return Promise.resolve(aDocument);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
aDocument.addEventListener("visibilitychange", function onVisibilityChanged() {
|
||||
aDocument.removeEventListener("visibilitychange", onVisibilityChanged);
|
||||
resolve(aDocument);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle between opening or hiding the Loop panel.
|
||||
*
|
||||
* @param {DOMEvent} [event] Optional event that triggered the call to this
|
||||
* function.
|
||||
* @param {String} [tabId] Optional name of the tab to select after the panel
|
||||
* has opened. Does nothing when the panel is hidden.
|
||||
* @return {Promise}
|
||||
*/
|
||||
togglePanel: function(event, tabId = null) {
|
||||
if (!this.panel) {
|
||||
// We're on the hidden window! What fun!
|
||||
let obs = win => {
|
||||
Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
|
||||
win.LoopUI.togglePanel(event, tabId);
|
||||
};
|
||||
Services.obs.addObserver(obs, "browser-delayed-startup-finished", false);
|
||||
return OpenBrowserWindow();
|
||||
}
|
||||
if (this.panel.state == "open") {
|
||||
return new Promise(resolve => {
|
||||
this.panel.hidePopup();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
return this.openCallPanel(event, tabId).then(doc => {
|
||||
let fm = Services.focus;
|
||||
fm.moveFocus(doc.defaultView, null, fm.MOVEFOCUS_FIRST, fm.FLAG_NOSCROLL);
|
||||
}).catch(err => {
|
||||
Cu.reportError(x);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the panel for Loop and sizes it appropriately.
|
||||
*
|
||||
* @param {event} event The event opening the panel, used to anchor
|
||||
* the panel to the button which triggers it.
|
||||
* @param {String} [tabId] Identifier of the tab to select when the panel is
|
||||
* opened. Example: 'rooms', 'contacts', etc.
|
||||
* @return {Promise}
|
||||
*/
|
||||
openCallPanel: function(event, tabId = null) {
|
||||
return new Promise((resolve) => {
|
||||
let callback = iframe => {
|
||||
// Helper function to show a specific tab view in the panel.
|
||||
function showTab() {
|
||||
if (!tabId) {
|
||||
resolve(LoopUI.promiseDocumentVisible(iframe.contentDocument));
|
||||
return;
|
||||
}
|
||||
|
||||
let win = iframe.contentWindow;
|
||||
let ev = new win.CustomEvent("UIAction", Cu.cloneInto({
|
||||
detail: {
|
||||
action: "selectTab",
|
||||
tab: tabId
|
||||
}
|
||||
}, win));
|
||||
win.dispatchEvent(ev);
|
||||
resolve(LoopUI.promiseDocumentVisible(iframe.contentDocument));
|
||||
}
|
||||
|
||||
// If the panel has been opened and initialized before, we can skip waiting
|
||||
// for the content to load - because it's already there.
|
||||
if (("contentWindow" in iframe) && iframe.contentWindow.document.readyState == "complete") {
|
||||
showTab();
|
||||
return;
|
||||
}
|
||||
|
||||
let documentDOMLoaded = () => {
|
||||
iframe.removeEventListener("DOMContentLoaded", documentDOMLoaded, true);
|
||||
// Handle window.close correctly on the panel.
|
||||
this.hookWindowCloseForPanelClose(iframe.contentWindow);
|
||||
iframe.contentWindow.addEventListener("loopPanelInitialized", function loopPanelInitialized() {
|
||||
iframe.contentWindow.removeEventListener("loopPanelInitialized",
|
||||
loopPanelInitialized);
|
||||
showTab();
|
||||
});
|
||||
};
|
||||
iframe.addEventListener("DOMContentLoaded", documentDOMLoaded, true);
|
||||
};
|
||||
|
||||
// Used to clear the temporary "login" state from the button.
|
||||
Services.obs.notifyObservers(null, "loop-status-changed", null);
|
||||
|
||||
this.shouldResumeTour().then((resume) => {
|
||||
if (resume) {
|
||||
// Assume the conversation with the visitor wasn't open since we would
|
||||
// have resumed the tour as soon as the visitor joined if it was (and
|
||||
// the pref would have been set to false already.
|
||||
this.MozLoopService.resumeTour("waiting");
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.LoopAPI.initialize();
|
||||
|
||||
let anchor = event ? event.target : this.toolbarButton.anchor;
|
||||
let setHeight = 410;
|
||||
if (gBrowser.selectedBrowser.getAttribute("remote") === "true") {
|
||||
setHeight = 262;
|
||||
}
|
||||
this.PanelFrame.showPopup(window, anchor,
|
||||
"loop", null, "about:looppanel",
|
||||
// Loop wants a fixed size for the panel. This also stops it dynamically resizing.
|
||||
{ width: 330, height: setHeight },
|
||||
callback);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Method to know whether actions to open the panel should instead resume the tour.
|
||||
*
|
||||
* We need the panel to be opened via UITour so that it gets @noautohide.
|
||||
*
|
||||
* @return {Promise} resolving with a {Boolean} of whether the tour should be resumed instead of
|
||||
* opening the panel.
|
||||
*/
|
||||
shouldResumeTour: Task.async(function* () {
|
||||
// Resume the FTU tour if this is the first time a room was joined by
|
||||
// someone else since the tour.
|
||||
if (!Services.prefs.getBoolPref("loop.gettingStarted.resumeOnFirstJoin")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.LoopRooms.participantsCount) {
|
||||
// Nobody is in the rooms
|
||||
return false;
|
||||
}
|
||||
|
||||
let roomsWithNonOwners = yield this.roomsWithNonOwners();
|
||||
if (!roomsWithNonOwners.length) {
|
||||
// We were the only one in a room but we want to know about someone else joining.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
/**
|
||||
* @return {Promise} resolved with an array of Rooms with participants (excluding owners)
|
||||
*/
|
||||
roomsWithNonOwners: function() {
|
||||
return new Promise(resolve => {
|
||||
this.LoopRooms.getAll((error, rooms) => {
|
||||
let roomsWithNonOwners = [];
|
||||
for (let room of rooms) {
|
||||
if (!("participants" in room)) {
|
||||
continue;
|
||||
}
|
||||
let numNonOwners = room.participants.filter(participant => !participant.owner).length;
|
||||
if (!numNonOwners) {
|
||||
continue;
|
||||
}
|
||||
roomsWithNonOwners.push(room);
|
||||
}
|
||||
resolve(roomsWithNonOwners);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggers the initialization of the loop service. Called by
|
||||
* delayedStartup.
|
||||
*/
|
||||
init: function() {
|
||||
// Add observer notifications before the service is initialized
|
||||
Services.obs.addObserver(this, "loop-status-changed", false);
|
||||
|
||||
// This is a promise for test purposes, but we don't want to be logging
|
||||
// expected errors to the console, so we catch them here.
|
||||
this.MozLoopService.initialize().catch(ex => {
|
||||
if (!ex.message ||
|
||||
(!ex.message.contains("not enabled") &&
|
||||
!ex.message.contains("not needed"))) {
|
||||
console.error(ex);
|
||||
}
|
||||
});
|
||||
this.updateToolbarState();
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
Services.obs.removeObserver(this, "loop-status-changed");
|
||||
},
|
||||
|
||||
// Implements nsIObserver
|
||||
observe: function(subject, topic, data) {
|
||||
if (topic != "loop-status-changed") {
|
||||
return;
|
||||
}
|
||||
this.updateToolbarState(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the toolbar/menu-button state to reflect Loop status.
|
||||
*
|
||||
* @param {string} [aReason] Some states are only shown if
|
||||
* a related reason is provided.
|
||||
*
|
||||
* aReason="login": Used after a login is completed
|
||||
* successfully. This is used so the state can be
|
||||
* temporarily shown until the next state change.
|
||||
*/
|
||||
updateToolbarState: function(aReason = null) {
|
||||
if (!this.toolbarButton.node) {
|
||||
return;
|
||||
}
|
||||
let state = "";
|
||||
let mozL10nId = "loop-call-button3";
|
||||
let suffix = ".tooltiptext";
|
||||
if (this.MozLoopService.errors.size) {
|
||||
state = "error";
|
||||
mozL10nId += "-error";
|
||||
} else if (this.MozLoopService.screenShareActive) {
|
||||
state = "action";
|
||||
mozL10nId += "-screensharing";
|
||||
} else if (aReason == "login" && this.MozLoopService.userProfile) {
|
||||
state = "active";
|
||||
mozL10nId += "-active";
|
||||
suffix += "2";
|
||||
} else if (this.MozLoopService.doNotDisturb) {
|
||||
state = "disabled";
|
||||
mozL10nId += "-donotdisturb";
|
||||
} else if (this.MozLoopService.roomsParticipantsCount > 0) {
|
||||
state = "active";
|
||||
this.roomsWithNonOwners().then(roomsWithNonOwners => {
|
||||
if (roomsWithNonOwners.length > 0) {
|
||||
mozL10nId += "-participantswaiting";
|
||||
} else {
|
||||
mozL10nId += "-active";
|
||||
}
|
||||
|
||||
suffix += "2";
|
||||
this.updateTooltiptext(mozL10nId + suffix);
|
||||
this.toolbarButton.node.setAttribute("state", state);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
suffix += "2";
|
||||
}
|
||||
|
||||
this.toolbarButton.node.setAttribute("state", state);
|
||||
this.updateTooltiptext(mozL10nId + suffix);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the tootltiptext to reflect Loop status.
|
||||
*
|
||||
* @param {string} [mozL10nId] l10n ID that refelct the current
|
||||
* Loop status.
|
||||
*/
|
||||
updateTooltiptext: function(mozL10nId) {
|
||||
this.toolbarButton.node.setAttribute("tooltiptext", mozL10nId);
|
||||
var tooltiptext = CustomizableUI.getLocalizedProperty(this.toolbarButton, "tooltiptext");
|
||||
this.toolbarButton.node.setAttribute("tooltiptext", tooltiptext);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a desktop notification when 'do not disturb' isn't enabled.
|
||||
*
|
||||
* @param {Object} options Set of options that may tweak the appearance and
|
||||
* behavior of the notification.
|
||||
* Option params:
|
||||
* - {String} title Notification title message
|
||||
* - {String} [message] Notification body text
|
||||
* - {String} [icon] Notification icon
|
||||
* - {String} [sound] Sound to play
|
||||
* - {String} [selectTab] Tab to select when the panel
|
||||
* opens
|
||||
* - {Function} [onclick] Callback to invoke when
|
||||
* the notification is clicked.
|
||||
* Opens the panel by default.
|
||||
*/
|
||||
showNotification: function(options) {
|
||||
if (this.MozLoopService.doNotDisturb) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.title) {
|
||||
throw new Error("Missing title, can not display notification");
|
||||
}
|
||||
|
||||
let notificationOptions = {
|
||||
body: options.message || ""
|
||||
};
|
||||
if (options.icon) {
|
||||
notificationOptions.icon = options.icon;
|
||||
}
|
||||
if (options.sound) {
|
||||
// This will not do anything, until bug bug 1105222 is resolved.
|
||||
notificationOptions.mozbehavior = {
|
||||
soundFile: ""
|
||||
};
|
||||
this.playSound(options.sound);
|
||||
}
|
||||
|
||||
let notification = new window.Notification(options.title, notificationOptions);
|
||||
notification.addEventListener("click", e => {
|
||||
if (window.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.focus();
|
||||
} catch (ex) {}
|
||||
|
||||
// We need a setTimeout here, otherwise the panel won't show after the
|
||||
// window received focus.
|
||||
window.setTimeout(() => {
|
||||
if (typeof options.onclick == "function") {
|
||||
options.onclick();
|
||||
} else {
|
||||
// Open the Loop panel as a default action.
|
||||
this.openCallPanel(null, options.selectTab || null);
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Play a sound in this window IF there's no sound playing yet.
|
||||
*
|
||||
* @param {String} name Name of the sound, like 'ringtone' or 'room-joined'
|
||||
*/
|
||||
playSound: function(name) {
|
||||
if (this.ActiveSound || this.MozLoopService.doNotDisturb) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeSound = new window.Audio();
|
||||
this.activeSound.src = `chrome://browser/content/loop/shared/sounds/${name}.ogg`;
|
||||
this.activeSound.load();
|
||||
this.activeSound.play();
|
||||
|
||||
this.activeSound.addEventListener("ended", () => this.activeSound = undefined, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start listening to selected tab changes and notify any content page that's
|
||||
* listening to 'BrowserSwitch' push messages.
|
||||
*
|
||||
* Push message parameters:
|
||||
* - {Integer} windowId The new windowId for the browser.
|
||||
*/
|
||||
startBrowserSharing: function() {
|
||||
if (!this._listeningToTabSelect) {
|
||||
gBrowser.tabContainer.addEventListener("TabSelect", this);
|
||||
this._listeningToTabSelect = true;
|
||||
}
|
||||
|
||||
this._maybeShowBrowserSharingInfoBar();
|
||||
|
||||
// Get the first window Id for the listener.
|
||||
this.LoopAPI.broadcastPushMessage("BrowserSwitch",
|
||||
gBrowser.selectedBrowser.outerWindowID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop listening to selected tab changes.
|
||||
*/
|
||||
stopBrowserSharing: function() {
|
||||
if (!this._listeningToTabSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._hideBrowserSharingInfoBar();
|
||||
gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
||||
this._listeningToTabSelect = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to fetch a localized string via the MozLoopService API.
|
||||
* It's currently inconveniently wrapped inside a string of stringified JSON.
|
||||
*
|
||||
* @param {String} key The element id to get strings for.
|
||||
* @return {String}
|
||||
*/
|
||||
_getString: function(key) {
|
||||
let str = this.MozLoopService.getStrings(key);
|
||||
if (str) {
|
||||
str = JSON.parse(str).textContent;
|
||||
}
|
||||
return str;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows an infobar notification at the top of the browser window that warns
|
||||
* the user that their browser tabs are being broadcasted through the current
|
||||
* conversation.
|
||||
*/
|
||||
_maybeShowBrowserSharingInfoBar: function() {
|
||||
this._hideBrowserSharingInfoBar();
|
||||
|
||||
// Don't show the infobar if it's been permanently disabled from the menu.
|
||||
if (!this.MozLoopService.getLoopPref(kPrefBrowserSharingInfoBar)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let box = gBrowser.getNotificationBox();
|
||||
let paused = false;
|
||||
let bar = box.appendNotification(
|
||||
this._getString("infobar_screenshare_browser_message"),
|
||||
kBrowserSharingNotificationId,
|
||||
// Icon is defined in browser theme CSS.
|
||||
null,
|
||||
box.PRIORITY_WARNING_LOW,
|
||||
[{
|
||||
label: this._getString("infobar_button_pause_label"),
|
||||
accessKey: this._getString("infobar_button_pause_accesskey"),
|
||||
isDefault: false,
|
||||
callback: (event, buttonInfo, buttonNode) => {
|
||||
paused = !paused;
|
||||
bar.label = paused ? this._getString("infobar_screenshare_paused_browser_message") :
|
||||
this._getString("infobar_screenshare_browser_message");
|
||||
bar.classList.toggle("paused", paused);
|
||||
buttonNode.label = paused ? this._getString("infobar_button_resume_label") :
|
||||
this._getString("infobar_button_pause_label");
|
||||
buttonNode.accessKey = paused ? this._getString("infobar_button_resume_accesskey") :
|
||||
this._getString("infobar_button_pause_accesskey");
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: this._getString("infobar_button_stop_label"),
|
||||
accessKey: this._getString("infobar_button_stop_accesskey"),
|
||||
isDefault: true,
|
||||
callback: () => {
|
||||
this._hideBrowserSharingInfoBar();
|
||||
LoopUI.MozLoopService.hangupAllChatWindows();
|
||||
}
|
||||
}]
|
||||
);
|
||||
|
||||
// Keep showing the notification bar until the user explicitly closes it.
|
||||
bar.persistence = -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides the infobar, permanantly if requested.
|
||||
*
|
||||
* @param {Boolean} permanently Flag that determines if the infobar will never
|
||||
* been shown again. Defaults to `false`.
|
||||
* @return {Boolean} |true| if the infobar was hidden here.
|
||||
*/
|
||||
_hideBrowserSharingInfoBar: function(permanently = false, browser) {
|
||||
browser = browser || gBrowser.selectedBrowser;
|
||||
let box = gBrowser.getNotificationBox(browser);
|
||||
let notification = box.getNotificationWithValue(kBrowserSharingNotificationId);
|
||||
let removed = false;
|
||||
if (notification) {
|
||||
box.removeNotification(notification);
|
||||
removed = true;
|
||||
}
|
||||
|
||||
if (permanently) {
|
||||
this.MozLoopService.setLoopPref(kPrefBrowserSharingInfoBar, false);
|
||||
}
|
||||
|
||||
return removed;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles events from gBrowser.
|
||||
*/
|
||||
handleEvent: function(event) {
|
||||
// We only should get "select" events.
|
||||
if (event.type != "TabSelect") {
|
||||
return;
|
||||
}
|
||||
|
||||
let wasVisible = false;
|
||||
// Hide the infobar from the previous tab.
|
||||
if (event.detail.previousTab) {
|
||||
wasVisible = this._hideBrowserSharingInfoBar(false,
|
||||
event.detail.previousTab.linkedBrowser);
|
||||
}
|
||||
|
||||
// We've changed the tab, so get the new window id.
|
||||
this.LoopAPI.broadcastPushMessage("BrowserSwitch",
|
||||
gBrowser.selectedBrowser.outerWindowID);
|
||||
|
||||
if (wasVisible) {
|
||||
// If the infobar was visible before, we should show it again after the
|
||||
// switch.
|
||||
this._maybeShowBrowserSharingInfoBar();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the favicon of the currently selected tab in the format of a data-uri.
|
||||
*
|
||||
* @param {Function} callback Function to be invoked with an error object as
|
||||
* its first argument when an error occurred or
|
||||
* a string as second argument when the favicon
|
||||
* has been fetched.
|
||||
*/
|
||||
getFavicon: function(callback) {
|
||||
let pageURI = gBrowser.selectedTab.linkedBrowser.currentURI.spec;
|
||||
// If the tab page’s url starts with http(s), fetch icon.
|
||||
if (!/^https?:/.test(pageURI)) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
this.PlacesUtils.promiseFaviconLinkUrl(pageURI).then(uri => {
|
||||
// We XHR the favicon to get a File object, which we can pass to the FileReader
|
||||
// object. The FileReader turns the File object into a data-uri.
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("get", uri.spec, true);
|
||||
xhr.responseType = "blob";
|
||||
xhr.overrideMimeType("image/x-icon");
|
||||
xhr.onload = () => {
|
||||
if (xhr.status != 200) {
|
||||
callback(new Error("Invalid status code received for favicon XHR: " + xhr.status));
|
||||
return;
|
||||
}
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.onload = reader.onload = () => callback(null, reader.result);
|
||||
reader.onerror = callback;
|
||||
reader.readAsDataURL(xhr.response);
|
||||
};
|
||||
xhr.onerror = callback;
|
||||
xhr.send();
|
||||
}).catch(err => {
|
||||
callback(err || new Error("No favicon found"));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "hookWindowCloseForPanelClose", "resource://gre/modules/MozSocialAPI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "LoopAPI", "resource:///modules/loop/MozLoopAPI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "LoopRooms", "resource:///modules/loop/LoopRooms.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "MozLoopService", "resource:///modules/loop/MozLoopService.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "PanelFrame", "resource:///modules/PanelFrame.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче