merge fx-team to mozilla-central a=merge
157
.eslintignore
Normal file
@ -0,0 +1,157 @@
|
||||
# Always ignore node_modules.
|
||||
**/node_modules/**/*.*
|
||||
|
||||
# Exclude expected objdirs.
|
||||
obj*/**
|
||||
|
||||
# We ignore all these directories by default, until we get them enabled.
|
||||
# If you are enabling a directory, please add directory specific exclusions
|
||||
# below.
|
||||
accessible/**
|
||||
addon-sdk/**
|
||||
b2g/**
|
||||
build/**
|
||||
caps/**
|
||||
chrome/**
|
||||
config/**
|
||||
db/**
|
||||
docshell/**
|
||||
dom/**
|
||||
editor/**
|
||||
embedding/**
|
||||
extensions/**
|
||||
gfx/**
|
||||
gradle/**
|
||||
hal/**
|
||||
image/**
|
||||
intl/**
|
||||
ipc/**
|
||||
js/**
|
||||
layout/**
|
||||
media/**
|
||||
memory/**
|
||||
mfbt/**
|
||||
modules/**
|
||||
mozglue/**
|
||||
netwerk/**
|
||||
nsprpub/**
|
||||
other-licenses/**
|
||||
parser/**
|
||||
probes/**
|
||||
python/**
|
||||
rdf/**
|
||||
security/**
|
||||
services/**
|
||||
startupcache/**
|
||||
storage/**
|
||||
testing/**
|
||||
toolkit/**
|
||||
tools/**
|
||||
uriloader/**
|
||||
view/**
|
||||
webapprt/**
|
||||
widget/**
|
||||
xpcom/**
|
||||
xpfe/**
|
||||
xulrunner/**
|
||||
|
||||
# browser/ exclusions
|
||||
browser/app/**
|
||||
browser/base/**
|
||||
browser/branding/**
|
||||
browser/components/**
|
||||
browser/config/**
|
||||
browser/docs/**
|
||||
browser/experiments/**
|
||||
browser/extensions/pdfjs/**
|
||||
browser/extensions/shumway/**
|
||||
browser/fuel/**
|
||||
browser/installer/**
|
||||
browser/locales/**
|
||||
browser/modules/**
|
||||
browser/themes/**
|
||||
|
||||
# Loop specific exclusions
|
||||
|
||||
# This file currently uses a non-standard (and not on a standards track)
|
||||
# if statement within catch.
|
||||
browser/extensions/loop/content/modules/MozLoopWorker.js
|
||||
# This file currently uses es7 features eslint issue:
|
||||
# https://github.com/eslint/espree/issues/125
|
||||
browser/extensions/loop/content/modules/MozLoopAPI.jsm
|
||||
# Need to fix the configuration for this.
|
||||
browser/extensions/loop/bootstrap.js
|
||||
# Need to drop the preprocessing (bug 1212428)
|
||||
browser/extensions/loop/content/preferences/prefs.js
|
||||
# Libs we don't need to check
|
||||
browser/extensions/loop/content/panels/vendor
|
||||
browser/extensions/loop/content/shared/vendor
|
||||
browser/extensions/loop/standalone/content/libs
|
||||
# Libs we don't need to check
|
||||
browser/extensions/loop/test/shared/vendor
|
||||
# Coverage files
|
||||
browser/extensions/loop/test/coverage
|
||||
# These are generated react files that we don't need to check
|
||||
browser/extensions/loop/content/panels/js/conversation.js
|
||||
browser/extensions/loop/content/panels/js/conversationViews.js
|
||||
browser/extensions/loop/content/panels/js/panel.js
|
||||
browser/extensions/loop/content/panels/js/roomViews.js
|
||||
browser/extensions/loop/content/panels/js/feedbackViews.js
|
||||
browser/extensions/loop/content/shared/js/textChatView.js
|
||||
browser/extensions/loop/content/shared/js/linkifiedTextView.js
|
||||
browser/extensions/loop/content/shared/js/views.js
|
||||
browser/extensions/loop/standalone/content/js/standaloneRoomViews.js
|
||||
browser/extensions/loop/standalone/content/js/webapp.js
|
||||
browser/extensions/loop/ui/ui-showcase.js
|
||||
# Don't need to check the built tree
|
||||
browser/extensions/loop/standalone/dist
|
||||
|
||||
# devtools/ exclusions
|
||||
# Ignore d3
|
||||
devtools/client/shared/d3.js
|
||||
devtools/client/webaudioeditor/lib/dagre-d3.js
|
||||
|
||||
# Ignore codemirror
|
||||
devtools/client/sourceeditor/codemirror/*.js
|
||||
devtools/client/sourceeditor/codemirror/**/*.js
|
||||
|
||||
# Ignore jquery test libs
|
||||
devtools/client/markupview/test/lib_*
|
||||
|
||||
# Ignore pre-processed files
|
||||
devtools/client/framework/toolbox-process-window.js
|
||||
devtools/client/performance/system.js
|
||||
devtools/client/webide/webide-prefs.js
|
||||
|
||||
# Ignore various libs
|
||||
devtools/shared/jsbeautify/*
|
||||
devtools/shared/acorn/*
|
||||
devtools/shared/tern/*
|
||||
devtools/shared/pretty-fast/*
|
||||
devtools/shared/sourcemap/*
|
||||
|
||||
# mobile/android/ exclusions
|
||||
mobile/android/chrome/content
|
||||
mobile/android/tests/
|
||||
|
||||
# Uses `#filter substitution`
|
||||
mobile/android/b2gdroid/app/b2gdroid.js
|
||||
mobile/android/app/mobile.js
|
||||
mobile/android/chrome/content/healthreport-prefs.js
|
||||
|
||||
# Uses `#expand`
|
||||
mobile/android/chrome/content/about.js
|
||||
|
||||
# Not much JS to lint and non-standard at that
|
||||
mobile/android/installer/
|
||||
mobile/android/locales/
|
||||
|
||||
# Pretty sure we're disabling this one anyway
|
||||
mobile/android/modules/ContactService.jsm
|
||||
|
||||
# es7 proposed: array comprehensions
|
||||
# https://github.com/eslint/espree/issues/125
|
||||
mobile/android/modules/WebappManager.jsm
|
||||
|
||||
# Non-standard `(catch ex if ...)`
|
||||
mobile/android/components/Snippets.js
|
10
.hgignore
@ -80,9 +80,15 @@ GRTAGS
|
||||
GSYMS
|
||||
GPATH
|
||||
|
||||
# Unit tests for Loop
|
||||
# Various items for Loop
|
||||
^browser/components/loop/standalone/content/config\.js$
|
||||
^browser/components/loop/standalone/node_modules/
|
||||
^browser/extensions/loop/.*/node_modules/
|
||||
^browser/extensions/loop/.*\.module-cache
|
||||
^browser/extensions/loop/test/coverage/desktop
|
||||
^browser/extensions/loop/test/coverage/shared_standalone
|
||||
^browser/extensions/loop/test/visual-regression/diff
|
||||
^browser/extensions/loop/test/visual-regression/new
|
||||
^browser/extensions/loop/test/visual-regression/refs
|
||||
|
||||
# Git clone directory for updating web-platform-tests
|
||||
^testing/web-platform/sync/
|
||||
|
@ -1376,42 +1376,6 @@ pref("shumway.disabled", true);
|
||||
// (This is intentionally on the high side; see bug 746055.)
|
||||
pref("image.mem.max_decoded_image_kb", 256000);
|
||||
|
||||
pref("loop.enabled", true);
|
||||
pref("loop.textChat.enabled", true);
|
||||
pref("loop.server", "https://loop.services.mozilla.com/v0");
|
||||
pref("loop.linkClicker.url", "https://hello.firefox.com/");
|
||||
pref("loop.gettingStarted.seen", false);
|
||||
pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
|
||||
pref("loop.gettingStarted.resumeOnFirstJoin", false);
|
||||
pref("loop.learnMoreUrl", "https://www.firefox.com/hello/");
|
||||
pref("loop.legal.ToS_url", "https://www.mozilla.org/about/legal/terms/firefox-hello/");
|
||||
pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/firefox-hello/");
|
||||
pref("loop.do_not_disturb", false);
|
||||
pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/ringtone.ogg");
|
||||
pref("loop.retry_delay.start", 60000);
|
||||
pref("loop.retry_delay.limit", 300000);
|
||||
pref("loop.ping.interval", 1800000);
|
||||
pref("loop.ping.timeout", 10000);
|
||||
pref("loop.feedback.baseUrl", "https://input.mozilla.org/api/v1/feedback");
|
||||
pref("loop.feedback.product", "Loop");
|
||||
pref("loop.debug.loglevel", "Error");
|
||||
pref("loop.debug.dispatcher", false);
|
||||
pref("loop.debug.sdk", false);
|
||||
pref("loop.debug.twoWayMediaTelemetry", false);
|
||||
pref("loop.feedback.dateLastSeenSec", 0);
|
||||
pref("loop.feedback.periodSec", 15770000); // 6 months.
|
||||
pref("loop.feedback.formURL", "https://www.mozilla.org/firefox/hello/npssurvey/");
|
||||
pref("loop.feedback.manualFormURL", "https://www.mozilla.org/firefox/hello/feedbacksurvey/");
|
||||
#ifdef DEBUG
|
||||
pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src * data:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*; media-src blob:");
|
||||
#else
|
||||
pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src * data:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net; media-src blob:");
|
||||
#endif
|
||||
pref("loop.fxa_oauth.tokendata", "");
|
||||
pref("loop.fxa_oauth.profile", "");
|
||||
pref("loop.support_url", "https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc");
|
||||
pref("loop.browserSharing.showInfoBar", true);
|
||||
|
||||
pref("social.sidebar.unload_timeout_ms", 10000);
|
||||
|
||||
// Activation from inside of share panel is possible if activationPanelEnabled
|
||||
|
@ -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");
|
@ -902,11 +902,6 @@ notification[value="translation"] {
|
||||
-moz-binding: url("chrome://browser/content/translation-infobar.xml#translationbar");
|
||||
}
|
||||
|
||||
/* Loop/ Hello */
|
||||
notification[value="loop-sharing-notification"] .close-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Social */
|
||||
/* Note the chatbox 'width' values are duplicated in socialchat.xml */
|
||||
chatbox {
|
||||
|
@ -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");
|
||||
|
@ -108,12 +108,12 @@ static RedirEntry kRedirMap[] = {
|
||||
{
|
||||
"debugging", "chrome://devtools/content/aboutdebugging/aboutdebugging.xhtml",
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
{ "loopconversation", "chrome://browser/content/loop/conversation.html",
|
||||
{ "loopconversation", "chrome://loop/content/panels/conversation.html",
|
||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
||||
nsIAboutModule::ALLOW_SCRIPT |
|
||||
nsIAboutModule::HIDE_FROM_ABOUTABOUT |
|
||||
nsIAboutModule::ENABLE_INDEXED_DB },
|
||||
{ "looppanel", "chrome://browser/content/loop/panel.html",
|
||||
{ "looppanel", "chrome://loop/content/panels/panel.html",
|
||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
||||
nsIAboutModule::ALLOW_SCRIPT |
|
||||
nsIAboutModule::HIDE_FROM_ABOUTABOUT |
|
||||
|
@ -950,42 +950,6 @@ const CustomizableWidgets = [
|
||||
let win = aEvent.view;
|
||||
win.MailIntegration.sendLinkForBrowser(win.gBrowser.selectedBrowser)
|
||||
}
|
||||
}, {
|
||||
id: "loop-button",
|
||||
type: "custom",
|
||||
label: "loop-call-button3.label",
|
||||
tooltiptext: "loop-call-button3.tooltiptext2",
|
||||
privateBrowsingTooltiptext: "loop-call-button3-pb.tooltiptext",
|
||||
defaultArea: CustomizableUI.AREA_NAVBAR,
|
||||
introducedInVersion: 4,
|
||||
onBuild: function(aDocument) {
|
||||
// If we're not supposed to see the button, return zip.
|
||||
if (!Services.prefs.getBoolPref("loop.enabled")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(aDocument.defaultView);
|
||||
|
||||
let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
|
||||
node.setAttribute("id", this.id);
|
||||
node.classList.add("toolbarbutton-1");
|
||||
node.classList.add("chromeclass-toolbar-additional");
|
||||
node.classList.add("badged-button");
|
||||
node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
|
||||
if (isWindowPrivate)
|
||||
node.setAttribute("disabled", "true");
|
||||
let tooltiptext = isWindowPrivate ?
|
||||
CustomizableUI.getLocalizedProperty(this, "privateBrowsingTooltiptext",
|
||||
[CustomizableUI.getLocalizedProperty(this, "label")]) :
|
||||
CustomizableUI.getLocalizedProperty(this, "tooltiptext");
|
||||
node.setAttribute("tooltiptext", tooltiptext);
|
||||
node.setAttribute("removable", "true");
|
||||
node.addEventListener("command", function(event) {
|
||||
aDocument.defaultView.LoopUI.togglePanel(event);
|
||||
});
|
||||
|
||||
return node;
|
||||
}
|
||||
}, {
|
||||
id: "web-apps-button",
|
||||
label: "web-apps-button.label",
|
||||
|
@ -1,3 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
|
||||
"@mozilla.org/browser/aboutnewtab-service;1",
|
||||
"nsIAboutNewTabService");
|
||||
@ -376,6 +378,14 @@ extensions.registerAPI((extension, context) => {
|
||||
runSafe(context, callback, TabManager.convert(extension, tab));
|
||||
},
|
||||
|
||||
getCurrent(callback) {
|
||||
let tab;
|
||||
if (context.tabId) {
|
||||
tab = TabManager.convert(extension, TabManager.getTab(context.tabId));
|
||||
}
|
||||
runSafe(context, callback, tab);
|
||||
},
|
||||
|
||||
getAllInWindow: function(...args) {
|
||||
let window, callback;
|
||||
if (args.length == 1) {
|
||||
|
@ -22,6 +22,7 @@ support-files =
|
||||
[browser_ext_getViews.js]
|
||||
[browser_ext_tabs_executeScript.js]
|
||||
[browser_ext_tabs_query.js]
|
||||
[browser_ext_tabs_getCurrent.js]
|
||||
[browser_ext_tabs_update.js]
|
||||
[browser_ext_tabs_onUpdated.js]
|
||||
[browser_ext_tabs_sendMessage.js]
|
||||
|
@ -38,18 +38,10 @@ add_task(function* testDisabled() {
|
||||
},
|
||||
});
|
||||
|
||||
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("ready");
|
||||
|
||||
yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
|
||||
|
||||
let elem = document.getElementById(browserActionId);
|
||||
|
||||
function click() {
|
||||
EventUtils.synthesizeMouseAtCenter(elem, {}, window);
|
||||
return new Promise(SimpleTest.executeSoon);
|
||||
}
|
||||
|
||||
yield click();
|
||||
yield clickBrowserAction(extension);
|
||||
|
||||
extension.sendMessage("check-clicked", true);
|
||||
yield extension.awaitMessage("next-test");
|
||||
@ -57,7 +49,7 @@ add_task(function* testDisabled() {
|
||||
extension.sendMessage("disable");
|
||||
yield extension.awaitMessage("next-test");
|
||||
|
||||
yield click();
|
||||
yield clickBrowserAction(extension);
|
||||
|
||||
extension.sendMessage("check-clicked", false);
|
||||
yield extension.awaitMessage("next-test");
|
||||
@ -65,7 +57,7 @@ add_task(function* testDisabled() {
|
||||
extension.sendMessage("enable");
|
||||
yield extension.awaitMessage("next-test");
|
||||
|
||||
yield click();
|
||||
yield clickBrowserAction(extension);
|
||||
|
||||
extension.sendMessage("check-clicked", true);
|
||||
yield extension.awaitMessage("next-test");
|
||||
|
@ -115,13 +115,10 @@ add_task(function* testPageActionPopup() {
|
||||
},
|
||||
});
|
||||
|
||||
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
|
||||
let panelId = makeWidgetId(extension.id) + "-panel";
|
||||
|
||||
extension.onMessage("send-click", () => {
|
||||
let button = document.getElementById(browserActionId);
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(button, {}, window);
|
||||
clickBrowserAction(extension);
|
||||
});
|
||||
|
||||
extension.onMessage("next-test", Task.async(function* () {
|
||||
|
@ -126,10 +126,7 @@ add_task(function* testPageActionPopup() {
|
||||
let panelId = makeWidgetId(extension.id) + "-panel";
|
||||
|
||||
extension.onMessage("send-click", () => {
|
||||
let image = document.getElementById(pageActionId);
|
||||
|
||||
let evt = new MouseEvent("click", {});
|
||||
image.dispatchEvent(evt);
|
||||
clickPageAction(extension);
|
||||
});
|
||||
|
||||
extension.onMessage("next-test", Task.async(function* () {
|
||||
@ -146,7 +143,8 @@ add_task(function* testPageActionPopup() {
|
||||
}));
|
||||
|
||||
|
||||
yield Promise.all([extension.startup(), extension.awaitFinish("pageaction-tests-done")]);
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("pageaction-tests-done");
|
||||
|
||||
yield extension.unload();
|
||||
|
||||
@ -192,21 +190,15 @@ add_task(function* testPageActionSecurity() {
|
||||
},
|
||||
});
|
||||
|
||||
yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("ready");
|
||||
|
||||
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
|
||||
let pageActionId = makeWidgetId(extension.id) + "-page-action";
|
||||
|
||||
let browserAction = document.getElementById(browserActionId);
|
||||
let evt = new CustomEvent("command", {});
|
||||
browserAction.dispatchEvent(evt);
|
||||
|
||||
let pageAction = document.getElementById(pageActionId);
|
||||
evt = new MouseEvent("click", {});
|
||||
pageAction.dispatchEvent(evt);
|
||||
yield clickBrowserAction(extension);
|
||||
yield clickPageAction(extension);
|
||||
|
||||
yield extension.unload();
|
||||
|
||||
let pageActionId = makeWidgetId(extension.id) + "-page-action";
|
||||
let node = document.getElementById(pageActionId);
|
||||
is(node, undefined, "pageAction image removed from document");
|
||||
|
||||
|
@ -0,0 +1,66 @@
|
||||
"use strict";
|
||||
|
||||
add_task(function* () {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"permissions": ["tabs"],
|
||||
|
||||
"browser_action": { "default_popup": "popup.html" },
|
||||
},
|
||||
|
||||
files: {
|
||||
"tab.js": function() {
|
||||
let url = document.location.href;
|
||||
|
||||
browser.tabs.getCurrent(currentTab => {
|
||||
browser.test.assertEq(currentTab.url, url, "getCurrent in non-active background tab");
|
||||
|
||||
// Activate the tab.
|
||||
browser.tabs.onActivated.addListener(function listener({ tabId }) {
|
||||
if (tabId == currentTab.id) {
|
||||
browser.tabs.onActivated.removeListener(listener);
|
||||
|
||||
browser.tabs.getCurrent(currentTab => {
|
||||
browser.test.assertEq(currentTab.id, tabId, "in active background tab");
|
||||
browser.test.assertEq(currentTab.url, url, "getCurrent in non-active background tab");
|
||||
|
||||
browser.test.sendMessage("tab-finished");
|
||||
browser.tabs.remove(tabId);
|
||||
});
|
||||
}
|
||||
});
|
||||
browser.tabs.update(currentTab.id, { active: true });
|
||||
});
|
||||
},
|
||||
|
||||
"popup.js": function() {
|
||||
browser.tabs.getCurrent(tab => {
|
||||
browser.test.assertEq(tab, undefined, "getCurrent in popup script");
|
||||
browser.test.sendMessage("popup-finished");
|
||||
});
|
||||
},
|
||||
|
||||
"tab.html": `<head><meta charset="utf-8"><script src="tab.js"></script></head>`,
|
||||
"popup.html": `<head><meta charset="utf-8"><script src="popup.js"></script></head>`,
|
||||
},
|
||||
|
||||
background: function() {
|
||||
browser.tabs.getCurrent(tab => {
|
||||
browser.test.assertEq(tab, undefined, "getCurrent in background script");
|
||||
browser.test.sendMessage("background-finished");
|
||||
});
|
||||
|
||||
browser.tabs.create({ url: "tab.html", active: false });
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
yield extension.awaitMessage("background-finished");
|
||||
yield extension.awaitMessage("tab-finished");
|
||||
|
||||
clickBrowserAction(extension);
|
||||
yield extension.awaitMessage("popup-finished");
|
||||
|
||||
yield extension.unload();
|
||||
});
|
@ -23,3 +23,27 @@ var focusWindow = Task.async(function* focusWindow(win)
|
||||
win.focus();
|
||||
yield promise;
|
||||
});
|
||||
|
||||
function clickBrowserAction(extension, win = window) {
|
||||
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
|
||||
let elem = win.document.getElementById(browserActionId);
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(elem, {}, win);
|
||||
return new Promise(SimpleTest.executeSoon);
|
||||
}
|
||||
|
||||
function clickPageAction(extension, win = window) {
|
||||
// This would normally be set automatically on navigation, and cleared
|
||||
// when the user types a value into the URL bar, to show and hide page
|
||||
// identity info and icons such as page action buttons.
|
||||
//
|
||||
// Unfortunately, that doesn't happen automatically in browser chrome
|
||||
// tests.
|
||||
SetPageProxyState("valid");
|
||||
|
||||
let pageActionId = makeWidgetId(extension.id) + "-page-action";
|
||||
let elem = win.document.getElementById(pageActionId);
|
||||
|
||||
EventUtils.synthesizeMouse(elem, 8, 8, {}, win);
|
||||
return new Promise(SimpleTest.executeSoon);
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- 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/. -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!-- Title is set in conversation.js -->
|
||||
<title></title>
|
||||
<link rel="stylesheet" type="text/css" href="loop/shared/css/reset.css">
|
||||
<link rel="stylesheet" type="text/css" href="loop/shared/css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="loop/shared/css/conversation.css">
|
||||
</head>
|
||||
<body class="fx-embedded">
|
||||
|
||||
<div id="messages"></div>
|
||||
|
||||
<div id="main"></div>
|
||||
|
||||
<script type="text/javascript" src="loop/libs/l10n.js"></script>
|
||||
<script type="text/javascript" src="loop/js/otconfig.js"></script>
|
||||
<script type="text/javascript" src="loop/libs/sdk.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/libs/react-0.13.3.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/libs/lodash-3.9.3.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/libs/backbone-1.2.1.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/libs/classnames-2.2.0.js"></script>
|
||||
|
||||
<script type="text/javascript" src="loop/shared/js/loopapi-client.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/utils.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/mixins.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/actions.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/validate.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/dispatcher.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/otSdkDriver.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/store.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/activeRoomStore.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/views.js"></script>
|
||||
<script type="text/javascript" src="loop/js/feedbackViews.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/textChatStore.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/textChatView.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/linkifiedTextView.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/urlRegExps.js"></script>
|
||||
<script type="text/javascript" src="loop/js/conversationAppStore.js"></script>
|
||||
<script type="text/javascript" src="loop/js/roomStore.js"></script>
|
||||
<script type="text/javascript" src="loop/js/roomViews.js"></script>
|
||||
<script type="text/javascript" src="loop/js/conversation.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,35 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- 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/. -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="loop/shared/css/reset.css">
|
||||
<link rel="stylesheet" type="text/css" href="loop/shared/css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="loop/css/panel.css">
|
||||
</head>
|
||||
<body class="panel">
|
||||
|
||||
<div id="main"></div>
|
||||
|
||||
<script type="text/javascript" src="loop/shared/libs/react-0.13.3.js"></script>
|
||||
<script type="text/javascript" src="loop/libs/l10n.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/libs/lodash-3.9.3.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/libs/backbone-1.2.1.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/libs/classnames-2.2.0.js"></script>
|
||||
|
||||
<script type="text/javascript" src="loop/shared/js/loopapi-client.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/utils.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/models.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/mixins.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/views.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/validate.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/actions.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/dispatcher.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/store.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/activeRoomStore.js"></script>
|
||||
<script type="text/javascript" src="loop/js/roomStore.js"></script>
|
||||
<script type="text/javascript" src="loop/js/panel.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,136 +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/.
|
||||
|
||||
browser.jar:
|
||||
# Desktop html files
|
||||
content/browser/loop/conversation.html (content/conversation.html)
|
||||
content/browser/loop/panel.html (content/panel.html)
|
||||
|
||||
# Desktop libs (see bottom of this file for TokBox sdk assets)
|
||||
content/browser/loop/libs/l10n.js (content/libs/l10n.js)
|
||||
|
||||
# Desktop script
|
||||
content/browser/loop/js/conversation.js (content/js/conversation.js)
|
||||
content/browser/loop/js/conversationAppStore.js (content/js/conversationAppStore.js)
|
||||
content/browser/loop/js/otconfig.js (content/js/otconfig.js)
|
||||
content/browser/loop/js/panel.js (content/js/panel.js)
|
||||
content/browser/loop/js/roomStore.js (content/js/roomStore.js)
|
||||
content/browser/loop/js/roomViews.js (content/js/roomViews.js)
|
||||
content/browser/loop/js/feedbackViews.js (content/js/feedbackViews.js)
|
||||
|
||||
# Desktop styles
|
||||
content/browser/loop/css/panel.css (content/css/panel.css)
|
||||
|
||||
# Shared styles
|
||||
content/browser/loop/shared/css/reset.css (content/shared/css/reset.css)
|
||||
content/browser/loop/shared/css/common.css (content/shared/css/common.css)
|
||||
content/browser/loop/shared/css/conversation.css (content/shared/css/conversation.css)
|
||||
|
||||
# Shared images
|
||||
content/browser/loop/shared/img/helloicon.svg (content/shared/img/helloicon.svg)
|
||||
content/browser/loop/shared/img/icon_32.png (content/shared/img/icon_32.png)
|
||||
content/browser/loop/shared/img/icon_64.png (content/shared/img/icon_64.png)
|
||||
content/browser/loop/shared/img/spinner.svg (content/shared/img/spinner.svg)
|
||||
# XXX could get rid of the png spinner usages and replace them with the svg
|
||||
# one?
|
||||
content/browser/loop/shared/img/spinner.png (content/shared/img/spinner.png)
|
||||
content/browser/loop/shared/img/spinner@2x.png (content/shared/img/spinner@2x.png)
|
||||
content/browser/loop/shared/img/sad_hello_icon_64x64.svg (content/shared/img/sad_hello_icon_64x64.svg)
|
||||
content/browser/loop/shared/img/chatbubble-arrow-left.svg (content/shared/img/chatbubble-arrow-left.svg)
|
||||
content/browser/loop/shared/img/chatbubble-arrow-right.svg (content/shared/img/chatbubble-arrow-right.svg)
|
||||
content/browser/loop/shared/img/facemute-14x14.png (content/shared/img/facemute-14x14.png)
|
||||
content/browser/loop/shared/img/facemute-14x14@2x.png (content/shared/img/facemute-14x14@2x.png)
|
||||
content/browser/loop/shared/img/hangup-inverse-14x14.png (content/shared/img/hangup-inverse-14x14.png)
|
||||
content/browser/loop/shared/img/hangup-inverse-14x14@2x.png (content/shared/img/hangup-inverse-14x14@2x.png)
|
||||
content/browser/loop/shared/img/mute-inverse-14x14.png (content/shared/img/mute-inverse-14x14.png)
|
||||
content/browser/loop/shared/img/mute-inverse-14x14@2x.png (content/shared/img/mute-inverse-14x14@2x.png)
|
||||
content/browser/loop/shared/img/svg/glyph-email-16x16.svg (content/shared/img/svg/glyph-email-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/glyph-facebook-16x16.svg (content/shared/img/svg/glyph-facebook-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/glyph-help-16x16.svg (content/shared/img/svg/glyph-help-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/glyph-link-16x16.svg (content/shared/img/svg/glyph-link-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/glyph-user-16x16.svg (content/shared/img/svg/glyph-user-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/exit.svg (content/shared/img/svg/exit.svg)
|
||||
content/browser/loop/shared/img/svg/audio.svg (content/shared/img/svg/audio.svg)
|
||||
content/browser/loop/shared/img/svg/audio-hover.svg (content/shared/img/svg/audio-hover.svg)
|
||||
content/browser/loop/shared/img/svg/audio-mute.svg (content/shared/img/svg/audio-mute.svg)
|
||||
content/browser/loop/shared/img/svg/audio-mute-hover.svg (content/shared/img/svg/audio-mute-hover.svg)
|
||||
content/browser/loop/shared/img/svg/video.svg (content/shared/img/svg/video.svg)
|
||||
content/browser/loop/shared/img/svg/video-hover.svg (content/shared/img/svg/video-hover.svg)
|
||||
content/browser/loop/shared/img/svg/video-mute.svg (content/shared/img/svg/video-mute.svg)
|
||||
content/browser/loop/shared/img/svg/video-mute-hover.svg (content/shared/img/svg/video-mute-hover.svg)
|
||||
content/browser/loop/shared/img/svg/settings.svg (content/shared/img/svg/settings.svg)
|
||||
content/browser/loop/shared/img/svg/settings-hover.svg (content/shared/img/svg/settings-hover.svg)
|
||||
content/browser/loop/shared/img/svg/sharing.svg (content/shared/img/svg/sharing.svg)
|
||||
content/browser/loop/shared/img/svg/sharing-active.svg (content/shared/img/svg/sharing-active.svg)
|
||||
content/browser/loop/shared/img/svg/sharing-pending.svg (content/shared/img/svg/sharing-pending.svg)
|
||||
content/browser/loop/shared/img/svg/sharing-hover.svg (content/shared/img/svg/sharing-hover.svg)
|
||||
content/browser/loop/shared/img/svg/media-group.svg (content/shared/img/svg/media-group.svg)
|
||||
content/browser/loop/shared/img/svg/media-group-left-hover.svg (content/shared/img/svg/media-group-left-hover.svg)
|
||||
content/browser/loop/shared/img/svg/media-group-right-hover.svg (content/shared/img/svg/media-group-right-hover.svg)
|
||||
content/browser/loop/shared/img/audio-call-avatar.svg (content/shared/img/audio-call-avatar.svg)
|
||||
content/browser/loop/shared/img/beta-ribbon.svg (content/shared/img/beta-ribbon.svg)
|
||||
content/browser/loop/shared/img/check.svg (content/shared/img/check.svg)
|
||||
content/browser/loop/shared/img/icons-10x10.svg (content/shared/img/icons-10x10.svg)
|
||||
content/browser/loop/shared/img/icons-14x14.svg (content/shared/img/icons-14x14.svg)
|
||||
content/browser/loop/shared/img/icons-16x16.svg (content/shared/img/icons-16x16.svg)
|
||||
content/browser/loop/shared/img/movistar.png (content/shared/img/movistar.png)
|
||||
content/browser/loop/shared/img/movistar@2x.png (content/shared/img/movistar@2x.png)
|
||||
content/browser/loop/shared/img/vivo.png (content/shared/img/vivo.png)
|
||||
content/browser/loop/shared/img/vivo@2x.png (content/shared/img/vivo@2x.png)
|
||||
content/browser/loop/shared/img/02.png (content/shared/img/02.png)
|
||||
content/browser/loop/shared/img/02@2x.png (content/shared/img/02@2x.png)
|
||||
content/browser/loop/shared/img/telefonica.png (content/shared/img/telefonica.png)
|
||||
content/browser/loop/shared/img/hello_logo.svg (content/shared/img/hello_logo.svg)
|
||||
content/browser/loop/shared/img/telefonica@2x.png (content/shared/img/telefonica@2x.png)
|
||||
content/browser/loop/shared/img/ellipsis-v.svg (content/shared/img/ellipsis-v.svg)
|
||||
content/browser/loop/shared/img/empty_conversations.svg (content/shared/img/empty_conversations.svg)
|
||||
content/browser/loop/shared/img/empty_search.svg (content/shared/img/empty_search.svg)
|
||||
content/browser/loop/shared/img/animated-spinner.svg (content/shared/img/animated-spinner.svg)
|
||||
content/browser/loop/shared/img/avatars.svg (content/shared/img/avatars.svg)
|
||||
content/browser/loop/shared/img/firefox-avatar.svg (content/shared/img/firefox-avatar.svg)
|
||||
content/browser/loop/shared/img/pause-12x12.svg (content/shared/img/pause-12x12.svg)
|
||||
content/browser/loop/shared/img/play-12x12.svg (content/shared/img/play-12x12.svg)
|
||||
content/browser/loop/shared/img/stop-12x12.svg (content/shared/img/stop-12x12.svg)
|
||||
|
||||
# Shared scripts
|
||||
content/browser/loop/shared/js/actions.js (content/shared/js/actions.js)
|
||||
content/browser/loop/shared/js/store.js (content/shared/js/store.js)
|
||||
content/browser/loop/shared/js/activeRoomStore.js (content/shared/js/activeRoomStore.js)
|
||||
content/browser/loop/shared/js/dispatcher.js (content/shared/js/dispatcher.js)
|
||||
content/browser/loop/shared/js/linkifiedTextView.js (content/shared/js/linkifiedTextView.js)
|
||||
content/browser/loop/shared/js/loopapi-client.js (content/shared/js/loopapi-client.js)
|
||||
content/browser/loop/shared/js/models.js (content/shared/js/models.js)
|
||||
content/browser/loop/shared/js/mixins.js (content/shared/js/mixins.js)
|
||||
content/browser/loop/shared/js/otSdkDriver.js (content/shared/js/otSdkDriver.js)
|
||||
content/browser/loop/shared/js/views.js (content/shared/js/views.js)
|
||||
content/browser/loop/shared/js/textChatStore.js (content/shared/js/textChatStore.js)
|
||||
content/browser/loop/shared/js/textChatView.js (content/shared/js/textChatView.js)
|
||||
content/browser/loop/shared/js/urlRegExps.js (content/shared/js/urlRegExps.js)
|
||||
content/browser/loop/shared/js/utils.js (content/shared/js/utils.js)
|
||||
content/browser/loop/shared/js/validate.js (content/shared/js/validate.js)
|
||||
|
||||
# Shared libs
|
||||
#ifdef DEBUG
|
||||
content/browser/loop/shared/libs/react-0.13.3.js (content/shared/libs/react-0.13.3.js)
|
||||
#else
|
||||
content/browser/loop/shared/libs/react-0.13.3.js (content/shared/libs/react-0.13.3-prod.js)
|
||||
#endif
|
||||
content/browser/loop/shared/libs/lodash-3.9.3.js (content/shared/libs/lodash-3.9.3.js)
|
||||
content/browser/loop/shared/libs/backbone-1.2.1.js (content/shared/libs/backbone-1.2.1.js)
|
||||
content/browser/loop/shared/libs/classnames-2.2.0.js (content/shared/libs/classnames-2.2.0.js)
|
||||
|
||||
# Shared sounds
|
||||
content/browser/loop/shared/sounds/ringtone.ogg (content/shared/sounds/ringtone.ogg)
|
||||
content/browser/loop/shared/sounds/connecting.ogg (content/shared/sounds/connecting.ogg)
|
||||
content/browser/loop/shared/sounds/connected.ogg (content/shared/sounds/connected.ogg)
|
||||
content/browser/loop/shared/sounds/terminated.ogg (content/shared/sounds/terminated.ogg)
|
||||
content/browser/loop/shared/sounds/room-joined.ogg (content/shared/sounds/room-joined.ogg)
|
||||
content/browser/loop/shared/sounds/room-joined-in.ogg (content/shared/sounds/room-joined-in.ogg)
|
||||
content/browser/loop/shared/sounds/room-left.ogg (content/shared/sounds/room-left.ogg)
|
||||
content/browser/loop/shared/sounds/failure.ogg (content/shared/sounds/failure.ogg)
|
||||
content/browser/loop/shared/sounds/message.ogg (content/shared/sounds/message.ogg)
|
||||
|
||||
# Partner SDK assets
|
||||
content/browser/loop/libs/sdk.js (content/shared/libs/sdk.js)
|
||||
content/browser/loop/sdk-content/js/dynamic_config.min.js (content/shared/libs/sdk-content/js/dynamic_config.min.js)
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "../.eslintrc-gecko"
|
||||
}
|
@ -11,7 +11,6 @@ DIRS += [
|
||||
'downloads',
|
||||
'extensions',
|
||||
'feeds',
|
||||
'loop',
|
||||
'migration',
|
||||
'newtab',
|
||||
'places',
|
||||
|
@ -9,8 +9,8 @@ var gContentWindow;
|
||||
var loopButton;
|
||||
var loopPanel = document.getElementById("loop-notification-panel");
|
||||
|
||||
const { LoopRooms } = Components.utils.import("resource:///modules/loop/LoopRooms.jsm", {});
|
||||
const { MozLoopServiceInternal } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
|
||||
const { LoopRooms } = Components.utils.import("chrome://loop/content/modules/LoopRooms.jsm", {});
|
||||
const { MozLoopServiceInternal } = Cu.import("chrome://loop/content/modules/MozLoopService.jsm", {});
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
|
@ -1,12 +1,16 @@
|
||||
# This file currently uses a non-standard (and not on a standards track)
|
||||
# if statement within catch.
|
||||
modules/MozLoopWorker.js
|
||||
content/modules/MozLoopWorker.js
|
||||
# This file currently uses es7 features eslint issue:
|
||||
# https://github.com/eslint/espree/issues/125
|
||||
modules/MozLoopAPI.jsm
|
||||
content/modules/MozLoopAPI.jsm
|
||||
# Need to fix the configuration for this.
|
||||
bootstrap.js
|
||||
# Need to drop the preprocessing (bug 1212428)
|
||||
content/preferences/prefs.js
|
||||
# Libs we don't need to check
|
||||
content/libs
|
||||
content/shared/libs
|
||||
content/panels/vendor
|
||||
content/shared/vendor
|
||||
standalone/content/libs
|
||||
standalone/node_modules
|
||||
# Libs we don't need to check
|
||||
@ -15,11 +19,11 @@ test/shared/vendor
|
||||
test/coverage
|
||||
test/node_modules
|
||||
# These are generated react files that we don't need to check
|
||||
content/js/conversation.js
|
||||
content/js/conversationViews.js
|
||||
content/js/panel.js
|
||||
content/js/roomViews.js
|
||||
content/js/feedbackViews.js
|
||||
content/panels/js/conversation.js
|
||||
content/panels/js/conversationViews.js
|
||||
content/panels/js/panel.js
|
||||
content/panels/js/roomViews.js
|
||||
content/panels/js/feedbackViews.js
|
||||
content/shared/js/textChatView.js
|
||||
content/shared/js/linkifiedTextView.js
|
||||
content/shared/js/views.js
|
@ -7,9 +7,10 @@
|
||||
"blockBindings": true,
|
||||
"destructuring": true,
|
||||
"generators": true,
|
||||
"objectLiteralShorthandMethods": true,
|
||||
"restParams": true,
|
||||
"spread": true,
|
||||
"objectLiteralShorthandMethods": true,
|
||||
"templateStrings": true,
|
||||
},
|
||||
"globals": {
|
||||
// Gecko + Loop Globals.
|
||||
@ -41,6 +42,7 @@
|
||||
"MozLoopPushHandler": true,
|
||||
"MozLoopService": true,
|
||||
"OS": false,
|
||||
"PrivateBrowsingUtils": false,
|
||||
"roomsPushNotification": true,
|
||||
"Services": false,
|
||||
"Social": false,
|
@ -22,9 +22,9 @@ using npm in order to compile the .jsx files into regular .js ones:
|
||||
npm install -g react-tools@0.12.2
|
||||
|
||||
Once installed, run build-jsx with the --watch option from
|
||||
browser/components/loop, eg.:
|
||||
browser/extensions/loop, eg.:
|
||||
|
||||
cd browser/components/loop
|
||||
cd browser/extensions/loop
|
||||
./build-jsx --watch
|
||||
|
||||
build-jsx can also be do a one-time compile pass instead of watching if
|
||||
@ -36,7 +36,7 @@ Hacking
|
||||
=======
|
||||
Please be sure to execute
|
||||
|
||||
browser/components/loop/run-all-loop-tests.sh
|
||||
browser/extensions/loop/run-all-loop-tests.sh
|
||||
|
||||
from the top level before requesting review on a patch.
|
||||
|
||||
@ -52,7 +52,7 @@ If you install eslint and the react plugin globally:
|
||||
npm install -g eslint
|
||||
npm install -g eslint-plugin-react
|
||||
|
||||
You can also run it by hand in the browser/components/loop directory:
|
||||
You can also run it by hand in the browser/extensions/loop directory:
|
||||
|
||||
eslint --ext .js --ext .jsx --ext .jsm .
|
||||
|
||||
@ -88,7 +88,7 @@ install that is properly configured. From the top-level gecko directory,
|
||||
execute:
|
||||
|
||||
export LOOP_SERVER=/Users/larry/src/loop-server
|
||||
./mach marionette-test browser/components/loop/test/functional/manifest.ini
|
||||
./mach marionette-test browser/extensions/loop/test/functional/manifest.ini
|
||||
|
||||
Once the automation is complete, we'll include this in run-all-loop-tests.sh
|
||||
as well.
|
849
browser/extensions/loop/bootstrap.js
vendored
Normal file
@ -0,0 +1,849 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
|
||||
|
||||
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
const kBrowserSharingNotificationId = "loop-sharing-notification";
|
||||
const kPrefBrowserSharingInfoBar = "browserSharing.showInfoBar";
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
|
||||
"resource:///modules/CustomizableUI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
/**
|
||||
* This window listener gets loaded into each browser.xul window and is used
|
||||
* to provide the required loop functions for the window.
|
||||
*/
|
||||
var WindowListener = {
|
||||
/**
|
||||
* Sets up the chrome integration within browser windows for Loop.
|
||||
*
|
||||
* @param {Object} window The window to inject the integration into.
|
||||
*/
|
||||
setupBrowserUI: function(window) {
|
||||
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 window.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(err);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
// Cleanup when the window unloads.
|
||||
window.addEventListener("unload", () => {
|
||||
this.uninit();
|
||||
});
|
||||
|
||||
// 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) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// 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://loop/content/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 = xhrClass.createInstance(Ci.nsIXMLHttpRequest);
|
||||
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", "chrome://loop/content/modules/MozLoopAPI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "LoopRooms", "chrome://loop/content/modules/LoopRooms.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "MozLoopService", "chrome://loop/content/modules/MozLoopService.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "PanelFrame", "resource:///modules/PanelFrame.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(LoopUI, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
LoopUI.init();
|
||||
window.LoopUI = LoopUI;
|
||||
},
|
||||
|
||||
tearDownBrowserUI: function(window) {
|
||||
let document = window.document;
|
||||
|
||||
// Take any steps to remove UI or anything from the browser window
|
||||
// document.getElementById() etc. will work here
|
||||
// XXX Add in tear-down of the panel.
|
||||
},
|
||||
|
||||
// nsIWindowMediatorListener functions.
|
||||
onOpenWindow: function(xulWindow) {
|
||||
// A new window has opened.
|
||||
let domWindow = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow);
|
||||
|
||||
// Wait for it to finish loading.
|
||||
domWindow.addEventListener("load", function listener() {
|
||||
domWindow.removeEventListener("load", listener, false);
|
||||
|
||||
// If this is a browser window then setup its UI.
|
||||
if (domWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser") {
|
||||
WindowListener.setupBrowserUI(domWindow);
|
||||
}
|
||||
}, false);
|
||||
},
|
||||
|
||||
onCloseWindow: function(xulWindow) {
|
||||
},
|
||||
|
||||
onWindowTitleChange: function(xulWindow, newTitle) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the loop button on the toolbar. Due to loop being a system-addon
|
||||
* CustomizableUI already has a placement location for the button, so that
|
||||
* we can be on the toolbar.
|
||||
*/
|
||||
function createLoopButton() {
|
||||
CustomizableUI.createWidget({
|
||||
id: "loop-button",
|
||||
type: "custom",
|
||||
label: "loop-call-button3.label",
|
||||
tooltiptext: "loop-call-button3.tooltiptext2",
|
||||
privateBrowsingTooltiptext: "loop-call-button3-pb.tooltiptext",
|
||||
defaultArea: CustomizableUI.AREA_NAVBAR,
|
||||
removable: true,
|
||||
onBuild: function(aDocument) {
|
||||
// If we're not supposed to see the button, return zip.
|
||||
if (!Services.prefs.getBoolPref("loop.enabled")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(aDocument.defaultView);
|
||||
|
||||
let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
|
||||
node.setAttribute("id", this.id);
|
||||
node.classList.add("toolbarbutton-1");
|
||||
node.classList.add("chromeclass-toolbar-additional");
|
||||
node.classList.add("badged-button");
|
||||
node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
|
||||
if (isWindowPrivate) {
|
||||
node.setAttribute("disabled", "true");
|
||||
}
|
||||
let tooltiptext = isWindowPrivate ?
|
||||
CustomizableUI.getLocalizedProperty(this, "privateBrowsingTooltiptext",
|
||||
[CustomizableUI.getLocalizedProperty(this, "label")]) :
|
||||
CustomizableUI.getLocalizedProperty(this, "tooltiptext");
|
||||
node.setAttribute("tooltiptext", tooltiptext);
|
||||
node.setAttribute("removable", "true");
|
||||
node.addEventListener("command", function(event) {
|
||||
aDocument.defaultView.LoopUI.togglePanel(event);
|
||||
});
|
||||
|
||||
return node;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the default preferences from the prefs file. This loads the preferences
|
||||
* into the default branch, so they don't appear as user preferences.
|
||||
*/
|
||||
function loadDefaultPrefs() {
|
||||
var branch = Services.prefs.getDefaultBranch("");
|
||||
Services.scriptloader.loadSubScript("chrome://loop/content/preferences/prefs.js", {
|
||||
pref: (key, val) => {
|
||||
switch (typeof val) {
|
||||
case "boolean":
|
||||
branch.setBoolPref(key, val);
|
||||
break;
|
||||
case "number":
|
||||
branch.setIntPref(key, val);
|
||||
break;
|
||||
case "string":
|
||||
branch.setCharPref(key, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the add-on is started, e.g. when installed or when Firefox starts.
|
||||
*/
|
||||
function startup() {
|
||||
loadDefaultPrefs();
|
||||
|
||||
createLoopButton();
|
||||
|
||||
// Attach to hidden window (for OS X).
|
||||
try {
|
||||
WindowListener.setupBrowserUI(Services.appShell.hiddenDOMWindow);
|
||||
} catch (ex) {
|
||||
// Hidden window didn't exist, so wait until startup is done.
|
||||
let topic = "browser-delayed-startup-finished";
|
||||
Services.obs.addObserver(function observer() {
|
||||
Services.obs.removeObserver(observer, topic);
|
||||
WindowListener.setupBrowserUI(Services.appShell.hiddenDOMWindow);
|
||||
}, topic, false);
|
||||
}
|
||||
|
||||
// Attach to existing browser windows, for modifying UI.
|
||||
let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
|
||||
let windows = wm.getEnumerator("navigator:browser");
|
||||
while (windows.hasMoreElements()) {
|
||||
let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
|
||||
WindowListener.setupBrowserUI(domWindow);
|
||||
}
|
||||
|
||||
// Wait for any new browser windows to open.
|
||||
wm.addListener(WindowListener);
|
||||
|
||||
// Load our stylesheets.
|
||||
let styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
|
||||
.getService(Components.interfaces.nsIStyleSheetService);
|
||||
let sheets = ["chrome://loop-shared/skin/loop.css",
|
||||
"chrome://loop/skin/platform.css"];
|
||||
for (let sheet of sheets) {
|
||||
let styleSheetURI = Services.io.newURI(sheet, null, null);
|
||||
// XXX We would love to specify AUTHOR_SHEET here and in shutdown, however
|
||||
// bug 1228542 prevents us from doing that as we'd cause a lot of assertions
|
||||
// in debug mode for tests. Once that is fixed, we should be able to change
|
||||
// this, and remove the !important attributes from our syle sheets.
|
||||
styleSheetService.loadAndRegisterSheet(styleSheetURI,
|
||||
styleSheetService.USER_SHEET);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the add-on is shutting down, could be for re-installation
|
||||
* or just uninstall.
|
||||
*/
|
||||
function shutdown() {
|
||||
// Close any open chat windows
|
||||
Cu.import("resource:///modules/Chat.jsm");
|
||||
let isLoopURL = ({ src }) => /^about:loopconversation#/.test(src);
|
||||
[...Chat.chatboxes].filter(isLoopURL).forEach(chatbox => {
|
||||
chatbox.content.contentWindow.close();
|
||||
});
|
||||
|
||||
// Detach from hidden window (for OS X).
|
||||
WindowListener.tearDownBrowserUI(Services.appShell.hiddenDOMWindow);
|
||||
|
||||
// Detach from browser windows.
|
||||
let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
|
||||
let windows = wm.getEnumerator("navigator:browser");
|
||||
while (windows.hasMoreElements()) {
|
||||
let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
|
||||
WindowListener.tearDownBrowserUI(domWindow);
|
||||
}
|
||||
|
||||
// Stop waiting for browser windows to open.
|
||||
wm.removeListener(WindowListener);
|
||||
|
||||
CustomizableUI.destroyWidget("loop-button");
|
||||
|
||||
// Unload stylesheets.
|
||||
let styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
|
||||
.getService(Components.interfaces.nsIStyleSheetService);
|
||||
let sheets = ["chrome://loop/content/addon/css/loop.css",
|
||||
"chrome://loop/skin/platform.css"];
|
||||
for (let sheet of sheets) {
|
||||
let styleSheetURI = Services.io.newURI(sheet, null, null);
|
||||
if (styleSheetService.sheetRegistered(styleSheetURI,
|
||||
styleSheetService.USER_SHEET)) {
|
||||
styleSheetService.unregisterSheet(styleSheetURI,
|
||||
styleSheetService.USER_SHEET);
|
||||
}
|
||||
}
|
||||
|
||||
// Unload modules.
|
||||
Cu.unload("chrome://loop/content/modules/MozLoopAPI.jsm");
|
||||
Cu.unload("chrome://loop/content/modules/LoopRooms.jsm");
|
||||
Cu.unload("chrome://loop/content/modules/MozLoopService.jsm");
|
||||
}
|
||||
|
||||
function install() {}
|
||||
|
||||
function uninstall() {}
|
@ -74,7 +74,7 @@ def find_react_command():
|
||||
return [node, jsx_path]
|
||||
|
||||
|
||||
SHARED_LIBS_DIR=os.path.join(os.path.dirname(__file__), "content", "shared", "libs")
|
||||
SHARED_LIBS_DIR=os.path.join(os.path.dirname(__file__), "content", "shared", "vendor")
|
||||
REACT_VERSION=find_react_version(SHARED_LIBS_DIR)
|
||||
|
||||
src_files = [] # files to be compiled
|
||||
@ -99,7 +99,7 @@ else:
|
||||
# parse the CLI arguments
|
||||
description = 'Loop build tool for JSX files. ' + \
|
||||
'Will scan entire loop directory and compile them in place. ' + \
|
||||
'Must be executed from browser/components/loop directory.'
|
||||
'Must be executed from browser/extensions/loop directory.'
|
||||
|
||||
parser = argparse.ArgumentParser(description=description)
|
||||
parser.add_argument('--watch', '-w', action='store_true', help='continuous' +
|
3
browser/extensions/loop/content/modules/.eslintrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../.eslintrc-gecko"
|
||||
}
|
@ -10,7 +10,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
const { MozLoopService, LOOP_SESSION_TYPE } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
|
||||
const { MozLoopService, LOOP_SESSION_TYPE } = Cu.import("chrome://loop/content/modules/MozLoopService.jsm", {});
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
|
||||
@ -27,11 +27,11 @@ XPCOMUtils.defineLazyGetter(this, "gLoopBundle", function() {
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopRoomsCache",
|
||||
"resource:///modules/loop/LoopRoomsCache.jsm");
|
||||
"chrome://loop/content/modules/LoopRoomsCache.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "loopUtils",
|
||||
"resource:///modules/loop/utils.js", "utils");
|
||||
"chrome://loop/content/modules/utils.js", "utils");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "loopCrypto",
|
||||
"resource:///modules/loop/crypto.js", "LoopCrypto");
|
||||
"chrome://loop/content/shared/js/crypto.js", "LoopCrypto");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ObjectUtils",
|
||||
"resource://gre/modules/ObjectUtils.jsm");
|
||||
|
@ -9,7 +9,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
const { MozLoopService, LOOP_SESSION_TYPE } =
|
||||
Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
|
||||
Cu.import("chrome://loop/content/modules/MozLoopService.jsm", {});
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
|
||||
"resource://services-common/utils.js");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
@ -9,8 +9,8 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/loop/MozLoopService.jsm");
|
||||
Cu.import("resource:///modules/loop/LoopRooms.jsm");
|
||||
Cu.import("chrome://loop/content/modules/MozLoopService.jsm");
|
||||
Cu.import("chrome://loop/content/modules/LoopRooms.jsm");
|
||||
Cu.importGlobalProperties(["Blob"]);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageMetadata",
|
@ -10,7 +10,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
const { MozLoopService } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
|
||||
const { MozLoopService } = Cu.import("chrome://loop/content/modules/MozLoopService.jsm", {});
|
||||
const consoleLog = MozLoopService.log;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["MozLoopPushHandler"];
|
@ -121,14 +121,14 @@ XPCOMUtils.defineConstant(this, "ROOM_DELETE", ROOM_DELETE);
|
||||
XPCOMUtils.defineConstant(this, "ROOM_CONTEXT_ADD", ROOM_CONTEXT_ADD);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopAPI",
|
||||
"resource:///modules/loop/MozLoopAPI.jsm");
|
||||
"chrome://loop/content/modules/MozLoopAPI.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "convertToRTCStatsReport",
|
||||
"resource://gre/modules/media/RTCStatsReport.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "loopUtils",
|
||||
"resource:///modules/loop/utils.js", "utils");
|
||||
"chrome://loop/content/modules/utils.js", "utils");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "loopCrypto",
|
||||
"resource:///modules/loop/crypto.js", "LoopCrypto");
|
||||
"chrome://loop/content/shared/js/crypto.js", "LoopCrypto");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Chat", "resource:///modules/Chat.jsm");
|
||||
|
||||
@ -151,13 +151,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "hookWindowCloseForPanelClose",
|
||||
"resource://gre/modules/MozSocialAPI.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopRooms",
|
||||
"resource:///modules/loop/LoopRooms.jsm");
|
||||
"chrome://loop/content/modules/LoopRooms.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "roomsPushNotification",
|
||||
"resource:///modules/loop/LoopRooms.jsm");
|
||||
"chrome://loop/content/modules/LoopRooms.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "MozLoopPushHandler",
|
||||
"resource:///modules/loop/MozLoopPushHandler.jsm");
|
||||
"chrome://loop/content/modules/MozLoopPushHandler.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UITour",
|
||||
"resource:///modules/UITour.jsm");
|
49
browser/extensions/loop/content/panels/conversation.html
Normal file
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- 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/. -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!-- Title is set in conversation.js -->
|
||||
<title></title>
|
||||
<base href="chrome://loop/content">
|
||||
<link rel="stylesheet" type="text/css" href="shared/css/reset.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/css/conversation.css">
|
||||
</head>
|
||||
<body class="fx-embedded">
|
||||
|
||||
<div id="messages"></div>
|
||||
|
||||
<div id="main"></div>
|
||||
|
||||
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
|
||||
<script type="text/javascript" src="panels/js/otconfig.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/sdk.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/react-0.13.3.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/lodash-3.9.3.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/backbone-1.2.1.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/classnames-2.2.0.js"></script>
|
||||
|
||||
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
|
||||
<script type="text/javascript" src="shared/js/utils.js"></script>
|
||||
<script type="text/javascript" src="shared/js/mixins.js"></script>
|
||||
<script type="text/javascript" src="shared/js/actions.js"></script>
|
||||
<script type="text/javascript" src="shared/js/validate.js"></script>
|
||||
<script type="text/javascript" src="shared/js/dispatcher.js"></script>
|
||||
<script type="text/javascript" src="shared/js/otSdkDriver.js"></script>
|
||||
<script type="text/javascript" src="shared/js/store.js"></script>
|
||||
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
|
||||
<script type="text/javascript" src="shared/js/views.js"></script>
|
||||
<script type="text/javascript" src="shared/js/textChatStore.js"></script>
|
||||
<script type="text/javascript" src="shared/js/textChatView.js"></script>
|
||||
<script type="text/javascript" src="shared/js/linkifiedTextView.js"></script>
|
||||
<script type="text/javascript" src="shared/js/urlRegExps.js"></script>
|
||||
<script type="text/javascript" src="panels/js/conversationAppStore.js"></script>
|
||||
<script type="text/javascript" src="panels/js/feedbackViews.js"></script>
|
||||
<script type="text/javascript" src="panels/js/roomStore.js"></script>
|
||||
<script type="text/javascript" src="panels/js/roomViews.js"></script>
|
||||
<script type="text/javascript" src="panels/js/conversation.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -12,7 +12,7 @@ body {
|
||||
|
||||
/* Beta Ribbon */
|
||||
.beta-ribbon {
|
||||
background: url("../shared/img/beta-ribbon.svg") no-repeat;
|
||||
background: url("../../shared/img/beta-ribbon.svg") no-repeat;
|
||||
background-size: 30px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
@ -158,7 +158,7 @@ body {
|
||||
transform: translateY(-50%);
|
||||
padding-top: 11rem;
|
||||
padding-bottom: 1rem;
|
||||
background-image: url("../shared/img/empty_conversations.svg");
|
||||
background-image: url("../../shared/img/empty_conversations.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: top center;
|
||||
}
|
||||
@ -324,11 +324,11 @@ body {
|
||||
}
|
||||
|
||||
.room-list > .room-entry:hover > h2 > .copy-link {
|
||||
background-image: url(../shared/img/icons-16x16.svg#copy);
|
||||
background-image: url(../../shared/img/icons-16x16.svg#copy);
|
||||
}
|
||||
|
||||
.room-list > .room-entry:hover > h2 > .delete-link {
|
||||
background-image: url(../shared/img/icons-16x16.svg#trash);
|
||||
background-image: url(../../shared/img/icons-16x16.svg#trash);
|
||||
}
|
||||
|
||||
/* scale this up to 1.1x and then back to the original size */
|
||||
@ -338,7 +338,7 @@ body {
|
||||
}
|
||||
|
||||
.room-list > .room-entry > h2 > .copy-link.checked {
|
||||
background: transparent url(../shared/img/icons-16x16.svg#checkmark);
|
||||
background: transparent url(../../shared/img/icons-16x16.svg#checkmark);
|
||||
animation: pulse .150s;
|
||||
animation-timing-function: ease-in-out;
|
||||
top: 0;
|
||||
@ -362,7 +362,7 @@ body {
|
||||
|
||||
/* Room entry edit button */
|
||||
.room-entry-context-edit-btn {
|
||||
background-image: url("../shared/img/icons-10x10.svg#edit-darkgrey");
|
||||
background-image: url("../../shared/img/icons-10x10.svg#edit-darkgrey");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 12px;
|
||||
@ -399,7 +399,7 @@ html[dir="rtl"] .room-entry-context-actions > .dropdown-menu {
|
||||
|
||||
.button-close {
|
||||
background-color: transparent;
|
||||
background-image: url(../shared/img/icons-10x10.svg#close);
|
||||
background-image: url(../../shared/img/icons-10x10.svg#close);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 8px 8px;
|
||||
border: none;
|
||||
@ -428,7 +428,7 @@ html[dir="rtl"] .room-entry-context-actions > .dropdown-menu {
|
||||
}
|
||||
|
||||
.spinner.busy {
|
||||
background-image: url(../shared/img/spinner.png);
|
||||
background-image: url(../../shared/img/spinner.png);
|
||||
animation-name: spinnerRotate;
|
||||
animation-duration: 1s;
|
||||
animation-timing-function: linear;
|
||||
@ -437,7 +437,7 @@ html[dir="rtl"] .room-entry-context-actions > .dropdown-menu {
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
.spinner.busy {
|
||||
background-image: url(../shared/img/spinner@2x.png);
|
||||
background-image: url(../../shared/img/spinner@2x.png);
|
||||
}
|
||||
}
|
||||
|
||||
@ -502,7 +502,7 @@ html[dir="rtl"] .generate-url-spinner {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
background-image: url("../shared/img/telefonica.png");
|
||||
background-image: url("../../shared/img/telefonica.png");
|
||||
background-size: 72px 20px;
|
||||
width: 72px;
|
||||
height: 20px;
|
||||
@ -510,21 +510,21 @@ html[dir="rtl"] .generate-url-spinner {
|
||||
|
||||
#powered-by-logo.en-GB,
|
||||
#powered-by-logo.de {
|
||||
background-image: url("../shared/img/02.png");
|
||||
background-image: url("../../shared/img/02.png");
|
||||
background-size: 21px 20px;
|
||||
width: 21px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#powered-by-logo.pt-BR {
|
||||
background-image: url("../shared/img/vivo.png");
|
||||
background-image: url("../../shared/img/vivo.png");
|
||||
background-size: 53px 26px;
|
||||
width: 53px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
#powered-by-logo[class^="es-"] {
|
||||
background-image: url("../shared/img/movistar.png");
|
||||
background-image: url("../../shared/img/movistar.png");
|
||||
background-size: 92px 20px;
|
||||
width: 92px;
|
||||
height: 20px;
|
||||
@ -532,20 +532,20 @@ html[dir="rtl"] .generate-url-spinner {
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
#powered-by-logo {
|
||||
background-image: url("../shared/img/telefonica@2x.png");
|
||||
background-image: url("../../shared/img/telefonica@2x.png");
|
||||
}
|
||||
|
||||
#powered-by-logo.en-GB,
|
||||
#powered-by-logo.de {
|
||||
background-image: url("../shared/img/02@2x.png");
|
||||
background-image: url("../../shared/img/02@2x.png");
|
||||
}
|
||||
|
||||
#powered-by-logo.pt-BR {
|
||||
background-image: url("../shared/img/vivo@2x.png");
|
||||
background-image: url("../../shared/img/vivo@2x.png");
|
||||
}
|
||||
|
||||
#powered-by-logo[class^="es-"] {
|
||||
background-image: url("../shared/img/movistar@2x.png");
|
||||
background-image: url("../../shared/img/movistar@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
@ -594,7 +594,7 @@ html[dir="rtl"] .generate-url-spinner {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
background: transparent url("../shared/img/icons-10x10.svg#settings-cog");
|
||||
background: transparent url("../../shared/img/icons-10x10.svg#settings-cog");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
@ -29,7 +29,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
return (
|
||||
React.createElement("div", {className: "fte-get-started-content"},
|
||||
React.createElement("header", {className: "fte-title"},
|
||||
React.createElement("img", {src: "loop/shared/img/hello_logo.svg"}),
|
||||
React.createElement("img", {src: "shared/img/hello_logo.svg"}),
|
||||
React.createElement("div", {className: "fte-subheader"},
|
||||
mozL10n.get("first_time_experience_subheading")
|
||||
)
|
||||
@ -379,7 +379,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
_renderDefaultIcon: function() {
|
||||
return (
|
||||
React.createElement("div", {className: "room-entry-context-item"},
|
||||
React.createElement("img", {src: "loop/shared/img/icons-16x16.svg#globe"})
|
||||
React.createElement("img", {src: "shared/img/icons-16x16.svg#globe"})
|
||||
)
|
||||
);
|
||||
},
|
||||
@ -390,7 +390,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
React.createElement("a", {href: roomUrl.location,
|
||||
onClick: this.handleClick,
|
||||
title: roomUrl.description},
|
||||
React.createElement("img", {src: roomUrl.thumbnail || "loop/shared/img/icons-16x16.svg#globe"})
|
||||
React.createElement("img", {src: roomUrl.thumbnail || "shared/img/icons-16x16.svg#globe"})
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -717,7 +717,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
React.createElement("div", {className: "room-list"},
|
||||
this._renderNewRoomButton(),
|
||||
React.createElement("div", {className: "room-list-loading"},
|
||||
React.createElement("img", {src: "loop/shared/img/animated-spinner.svg"})
|
||||
React.createElement("img", {src: "shared/img/animated-spinner.svg"})
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -884,7 +884,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
return (
|
||||
React.createElement("div", {className: "error-content"},
|
||||
React.createElement("header", {className: "error-title"},
|
||||
React.createElement("img", {src: "loop/shared/img/sad_hello_icon_64x64.svg"}),
|
||||
React.createElement("img", {src: "shared/img/sad_hello_icon_64x64.svg"}),
|
||||
React.createElement("p", {className: "error-subheader"},
|
||||
mozL10n.get("e10s_not_supported_subheading", {
|
||||
brandShortname: mozL10n.get("clientShortname2")
|
@ -29,7 +29,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
return (
|
||||
<div className="fte-get-started-content">
|
||||
<header className="fte-title">
|
||||
<img src="loop/shared/img/hello_logo.svg" />
|
||||
<img src="shared/img/hello_logo.svg" />
|
||||
<div className="fte-subheader">
|
||||
{mozL10n.get("first_time_experience_subheading")}
|
||||
</div>
|
||||
@ -379,7 +379,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
_renderDefaultIcon: function() {
|
||||
return (
|
||||
<div className="room-entry-context-item">
|
||||
<img src="loop/shared/img/icons-16x16.svg#globe" />
|
||||
<img src="shared/img/icons-16x16.svg#globe" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@ -390,7 +390,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
<a href={roomUrl.location}
|
||||
onClick={this.handleClick}
|
||||
title={roomUrl.description}>
|
||||
<img src={roomUrl.thumbnail || "loop/shared/img/icons-16x16.svg#globe"} />
|
||||
<img src={roomUrl.thumbnail || "shared/img/icons-16x16.svg#globe"} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
@ -717,7 +717,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
<div className="room-list">
|
||||
{this._renderNewRoomButton()}
|
||||
<div className="room-list-loading">
|
||||
<img src="loop/shared/img/animated-spinner.svg" />
|
||||
<img src="shared/img/animated-spinner.svg" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -884,7 +884,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
return (
|
||||
<div className="error-content">
|
||||
<header className="error-title">
|
||||
<img src="loop/shared/img/sad_hello_icon_64x64.svg" />
|
||||
<img src="shared/img/sad_hello_icon_64x64.svg" />
|
||||
<p className="error-subheader">
|
||||
{mozL10n.get("e10s_not_supported_subheading", {
|
||||
brandShortname: mozL10n.get("clientShortname2")
|
@ -346,14 +346,14 @@ loop.roomViews = (function(mozL10n) {
|
||||
"triggered": this.state.copiedUrl
|
||||
}),
|
||||
onClick: this.handleCopyButtonClick},
|
||||
React.createElement("img", {src: "loop/shared/img/svg/glyph-link-16x16.svg"}),
|
||||
React.createElement("img", {src: "shared/img/glyph-link-16x16.svg"}),
|
||||
React.createElement("p", null, mozL10n.get(this.state.copiedUrl ?
|
||||
"invite_copied_link_button" : "invite_copy_link_button"))
|
||||
),
|
||||
React.createElement("div", {className: "btn-email invite-button",
|
||||
onClick: this.handleEmailButtonClick,
|
||||
onMouseOver: this.resetTriggeredButtons},
|
||||
React.createElement("img", {src: "loop/shared/img/svg/glyph-email-16x16.svg"}),
|
||||
React.createElement("img", {src: "shared/img/glyph-email-16x16.svg"}),
|
||||
React.createElement("p", null, mozL10n.get("invite_email_link_button"))
|
||||
)
|
||||
),
|
||||
@ -536,7 +536,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
}
|
||||
|
||||
var url = this._getURL();
|
||||
var thumbnail = url && url.thumbnail || "loop/shared/img/icons-16x16.svg#globe";
|
||||
var thumbnail = url && url.thumbnail || "shared/img/icons-16x16.svg#globe";
|
||||
var urlDescription = url && url.description || "";
|
||||
var location = url && url.location || "";
|
||||
|
@ -346,14 +346,14 @@ loop.roomViews = (function(mozL10n) {
|
||||
"triggered": this.state.copiedUrl
|
||||
})}
|
||||
onClick={this.handleCopyButtonClick}>
|
||||
<img src="loop/shared/img/svg/glyph-link-16x16.svg" />
|
||||
<img src="shared/img/glyph-link-16x16.svg" />
|
||||
<p>{mozL10n.get(this.state.copiedUrl ?
|
||||
"invite_copied_link_button" : "invite_copy_link_button")}</p>
|
||||
</div>
|
||||
<div className="btn-email invite-button"
|
||||
onClick={this.handleEmailButtonClick}
|
||||
onMouseOver={this.resetTriggeredButtons}>
|
||||
<img src="loop/shared/img/svg/glyph-email-16x16.svg" />
|
||||
<img src="shared/img/glyph-email-16x16.svg" />
|
||||
<p>{mozL10n.get("invite_email_link_button")}</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -536,7 +536,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
}
|
||||
|
||||
var url = this._getURL();
|
||||
var thumbnail = url && url.thumbnail || "loop/shared/img/icons-16x16.svg#globe";
|
||||
var thumbnail = url && url.thumbnail || "shared/img/icons-16x16.svg#globe";
|
||||
var urlDescription = url && url.description || "";
|
||||
var location = url && url.location || "";
|
||||
|
36
browser/extensions/loop/content/panels/panel.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- 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/. -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<base href="chrome://loop/content">
|
||||
<link rel="stylesheet" type="text/css" href="shared/css/reset.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="panels/css/panel.css">
|
||||
</head>
|
||||
<body class="panel">
|
||||
|
||||
<div id="main"></div>
|
||||
|
||||
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/react-0.13.3.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/lodash-3.9.3.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/backbone-1.2.1.js"></script>
|
||||
<script type="text/javascript" src="shared/vendor/classnames-2.2.0.js"></script>
|
||||
|
||||
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
|
||||
<script type="text/javascript" src="shared/js/utils.js"></script>
|
||||
<script type="text/javascript" src="shared/js/models.js"></script>
|
||||
<script type="text/javascript" src="shared/js/mixins.js"></script>
|
||||
<script type="text/javascript" src="shared/js/views.js"></script>
|
||||
<script type="text/javascript" src="shared/js/validate.js"></script>
|
||||
<script type="text/javascript" src="shared/js/actions.js"></script>
|
||||
<script type="text/javascript" src="shared/js/dispatcher.js"></script>
|
||||
<script type="text/javascript" src="shared/js/store.js"></script>
|
||||
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
|
||||
<script type="text/javascript" src="panels/js/roomStore.js"></script>
|
||||
<script type="text/javascript" src="panels/js/panel.js"></script>
|
||||
</body>
|
||||
</html>
|
33
browser/extensions/loop/content/preferences/prefs.js
Normal file
@ -0,0 +1,33 @@
|
||||
pref("loop.enabled", true);
|
||||
pref("loop.textChat.enabled", true);
|
||||
pref("loop.server", "https://loop.services.mozilla.com/v0");
|
||||
pref("loop.linkClicker.url", "https://hello.firefox.com/");
|
||||
pref("loop.gettingStarted.seen", false);
|
||||
pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
|
||||
pref("loop.gettingStarted.resumeOnFirstJoin", false);
|
||||
pref("loop.learnMoreUrl", "https://www.firefox.com/hello/");
|
||||
pref("loop.legal.ToS_url", "https://www.mozilla.org/about/legal/terms/firefox-hello/");
|
||||
pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/firefox-hello/");
|
||||
pref("loop.do_not_disturb", false);
|
||||
pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/ringtone.ogg");
|
||||
pref("loop.retry_delay.start", 60000);
|
||||
pref("loop.retry_delay.limit", 300000);
|
||||
pref("loop.ping.interval", 1800000);
|
||||
pref("loop.ping.timeout", 10000);
|
||||
pref("loop.debug.loglevel", "Error");
|
||||
pref("loop.debug.dispatcher", false);
|
||||
pref("loop.debug.sdk", false);
|
||||
pref("loop.debug.twoWayMediaTelemetry", false);
|
||||
pref("loop.feedback.dateLastSeenSec", 0);
|
||||
pref("loop.feedback.periodSec", 15770000); // 6 months.
|
||||
pref("loop.feedback.formURL", "https://www.mozilla.org/firefox/hello/npssurvey/");
|
||||
pref("loop.feedback.manualFormURL", "https://www.mozilla.org/firefox/hello/feedbacksurvey/");
|
||||
#ifdef DEBUG
|
||||
pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src * data:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*; media-src blob:");
|
||||
#else
|
||||
pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src * data:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net; media-src blob:");
|
||||
#endif
|
||||
pref("loop.fxa_oauth.tokendata", "");
|
||||
pref("loop.fxa_oauth.profile", "");
|
||||
pref("loop.support_url", "https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc");
|
||||
pref("loop.browserSharing.showInfoBar", true);
|
@ -65,7 +65,7 @@ html[dir="rtl"] .conversation-toolbar > li {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-color: transparent;
|
||||
background-image: url("../img/svg/media-group.svg");
|
||||
background-image: url("../img/media-group.svg");
|
||||
background-size: cover;
|
||||
height: 28px;
|
||||
width: 67px;
|
||||
@ -73,25 +73,25 @@ html[dir="rtl"] .conversation-toolbar > li {
|
||||
|
||||
.conversation-toolbar-media-btn-group-box > button:last-child:active,
|
||||
.conversation-toolbar-media-btn-group-box > button:last-child:hover {
|
||||
background-image: url("../img/svg/media-group-right-hover.svg");
|
||||
background-image: url("../img/media-group-right-hover.svg");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .conversation-toolbar-media-btn-group-box > button:last-child:active,
|
||||
html[dir="rtl"] .conversation-toolbar-media-btn-group-box > button:last-child:hover {
|
||||
background-image: url("../img/svg/media-group-left-hover.svg");
|
||||
background-image: url("../img/media-group-left-hover.svg");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.conversation-toolbar-media-btn-group-box > button:first-child:active,
|
||||
.conversation-toolbar-media-btn-group-box > button:first-child:hover {
|
||||
background-image: url("../img/svg/media-group-left-hover.svg");
|
||||
background-image: url("../img/media-group-left-hover.svg");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .conversation-toolbar-media-btn-group-box > button:first-child:active,
|
||||
html[dir="rtl"] .conversation-toolbar-media-btn-group-box > button:first-child:hover {
|
||||
background-image: url("../img/svg/media-group-right-hover.svg");
|
||||
background-image: url("../img/media-group-right-hover.svg");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
@ -106,76 +106,76 @@ html[dir="rtl"] .conversation-toolbar-btn-box.btn-edit-entry {
|
||||
/* conversationViews.jsx */
|
||||
|
||||
.conversation-toolbar .btn-hangup {
|
||||
background-image: url("../img/svg/exit.svg");
|
||||
background-image: url("../img/exit.svg");
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Audio mute button */
|
||||
.btn-mute-audio:after {
|
||||
content: url("../img/svg/audio.svg");
|
||||
content: url("../img/audio.svg");
|
||||
}
|
||||
|
||||
.btn-mute-audio.muted:after {
|
||||
content: url("../img/svg/audio-mute.svg");
|
||||
content: url("../img/audio-mute.svg");
|
||||
}
|
||||
.btn-mute-audio:hover:after,
|
||||
.btn-mute-audio:active:after {
|
||||
content: url("../img/svg/audio-hover.svg");
|
||||
content: url("../img/audio-hover.svg");
|
||||
}
|
||||
|
||||
.btn-mute-audio.muted:hover:after,
|
||||
.btn-mute-audio.muted:active:after {
|
||||
content: url("../img/svg/audio-mute-hover.svg");
|
||||
content: url("../img/audio-mute-hover.svg");
|
||||
}
|
||||
|
||||
/* Video mute button */
|
||||
.btn-mute-video:after {
|
||||
content: url("../img/svg/video.svg");
|
||||
content: url("../img/video.svg");
|
||||
}
|
||||
|
||||
.btn-mute-video:active:after,
|
||||
.btn-mute-video:hover:after {
|
||||
content: url("../img/svg/video-hover.svg");
|
||||
content: url("../img/video-hover.svg");
|
||||
}
|
||||
|
||||
.btn-mute-video.muted:after {
|
||||
content: url("../img/svg/video-mute.svg");
|
||||
content: url("../img/video-mute.svg");
|
||||
}
|
||||
|
||||
.btn-mute-video.muted:hover:after,
|
||||
.btn-mute-video.muted:active:after {
|
||||
content: url("../img/svg/video-mute-hover.svg");
|
||||
content: url("../img/video-mute-hover.svg");
|
||||
}
|
||||
|
||||
.btn-settings {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background-size: 28px;
|
||||
background-image: url("../img/svg/settings.svg");
|
||||
background-image: url("../img/settings.svg");
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.btn-settings:hover,
|
||||
.btn-settings:active {
|
||||
background-image: url("../img/svg/settings-hover.svg");
|
||||
background-image: url("../img/settings-hover.svg");
|
||||
}
|
||||
|
||||
.btn-screen-share {
|
||||
background-image: url("../img/svg/sharing.svg");
|
||||
background-image: url("../img/sharing.svg");
|
||||
}
|
||||
|
||||
.btn-screen-share:hover,
|
||||
.btn-screen-share:active {
|
||||
background-image: url("../img/svg/sharing-hover.svg");
|
||||
background-image: url("../img/sharing-hover.svg");
|
||||
}
|
||||
|
||||
.btn-screen-share.active {
|
||||
background-image: url("../img/svg/sharing-active.svg");
|
||||
background-image: url("../img/sharing-active.svg");
|
||||
}
|
||||
|
||||
.btn-screen-share.disabled {
|
||||
/* The screen share button is in its pending state when its disabled. */
|
||||
background-image: url("../img/svg/sharing-pending.svg");
|
||||
background-image: url("../img/sharing-pending.svg");
|
||||
}
|
||||
|
||||
/* General Call (incoming or outgoing). */
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 700 B After Width: | Height: | Size: 700 B |
Before Width: | Height: | Size: 424 B After Width: | Height: | Size: 424 B |
Before Width: | Height: | Size: 536 B After Width: | Height: | Size: 536 B |
Before Width: | Height: | Size: 532 B After Width: | Height: | Size: 532 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 535 B After Width: | Height: | Size: 535 B |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 969 B After Width: | Height: | Size: 969 B |
Before Width: | Height: | Size: 886 B After Width: | Height: | Size: 886 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 225 B |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 218 B |
Before Width: | Height: | Size: 373 B After Width: | Height: | Size: 373 B |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 635 B After Width: | Height: | Size: 635 B |
Before Width: | Height: | Size: 348 B After Width: | Height: | Size: 348 B |
Before Width: | Height: | Size: 451 B After Width: | Height: | Size: 451 B |
Before Width: | Height: | Size: 832 B After Width: | Height: | Size: 832 B |
Before Width: | Height: | Size: 556 B After Width: | Height: | Size: 556 B |
Before Width: | Height: | Size: 188 B After Width: | Height: | Size: 188 B |
Before Width: | Height: | Size: 330 B After Width: | Height: | Size: 330 B |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 599 B After Width: | Height: | Size: 599 B |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 506 B After Width: | Height: | Size: 506 B |
Before Width: | Height: | Size: 498 B After Width: | Height: | Size: 498 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |