mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 10:44:56 +00:00
Bug 1589182 - remove mobile/android/extensions/ and /mobile/android/chrome/content (Fennec leftovers); r=snorp,agi
Remove mobile/android/extensions/ and /mobile/android/chrome/content from mozilla-central (Fennec leftovers) Differential Revision: https://phabricator.services.mozilla.com/D51194 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
407ce752f5
commit
dea46a2eed
@ -222,9 +222,6 @@ mobile/android/tests/browser/chrome/tp5/
|
||||
mobile/android/app/mobile.js
|
||||
mobile/android/app/geckoview-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/
|
||||
|
@ -1,12 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"rules": {
|
||||
"complexity": ["error", 20],
|
||||
|
||||
// Disabled stuff
|
||||
"no-console": 0, // TODO: Can we use console?
|
||||
"no-cond-assign": 0,
|
||||
"no-fallthrough": 0,
|
||||
}
|
||||
};
|
@ -1,857 +0,0 @@
|
||||
// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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";
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"PageActions",
|
||||
"resource://gre/modules/PageActions.jsm"
|
||||
);
|
||||
|
||||
// Define service devices. We should consider moving these to their respective
|
||||
// JSM files, but we left them here to allow for better lazy JSM loading.
|
||||
var rokuDevice = {
|
||||
id: "roku:ecp",
|
||||
target: "roku:ecp",
|
||||
factory: function(aService) {
|
||||
const { RokuApp } = ChromeUtils.import(
|
||||
"resource://gre/modules/RokuApp.jsm"
|
||||
);
|
||||
return new RokuApp(aService);
|
||||
},
|
||||
types: ["video/mp4"],
|
||||
extensions: ["mp4"],
|
||||
};
|
||||
|
||||
var mediaPlayerDevice = {
|
||||
id: "media:router",
|
||||
target: "media:router",
|
||||
factory: function(aService) {
|
||||
const { MediaPlayerApp } = ChromeUtils.import(
|
||||
"resource://gre/modules/MediaPlayerApp.jsm"
|
||||
);
|
||||
return new MediaPlayerApp(aService);
|
||||
},
|
||||
types: ["video/mp4", "video/webm", "application/x-mpegurl"],
|
||||
extensions: ["mp4", "webm", "m3u", "m3u8"],
|
||||
init: function() {
|
||||
GlobalEventDispatcher.registerListener(this, [
|
||||
"MediaPlayer:Added",
|
||||
"MediaPlayer:Changed",
|
||||
"MediaPlayer:Removed",
|
||||
]);
|
||||
},
|
||||
onEvent: function(event, data, callback) {
|
||||
if (event === "MediaPlayer:Added") {
|
||||
let service = this.toService(data);
|
||||
SimpleServiceDiscovery.addService(service);
|
||||
} else if (event === "MediaPlayer:Changed") {
|
||||
let service = this.toService(data);
|
||||
SimpleServiceDiscovery.updateService(service);
|
||||
} else if (event === "MediaPlayer:Removed") {
|
||||
SimpleServiceDiscovery.removeService(data.id);
|
||||
}
|
||||
},
|
||||
toService: function(display) {
|
||||
// Convert the native data into something matching what is created in _processService()
|
||||
return {
|
||||
location: display.location,
|
||||
target: "media:router",
|
||||
friendlyName: display.friendlyName,
|
||||
uuid: display.uuid,
|
||||
manufacturer: display.manufacturer,
|
||||
modelName: display.modelName,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
var CastingApps = {
|
||||
_castMenuId: -1,
|
||||
_blocked: null,
|
||||
_bound: null,
|
||||
_interval: 120 * 1000, // 120 seconds
|
||||
|
||||
init: function ca_init() {
|
||||
if (!this.isCastingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register targets
|
||||
SimpleServiceDiscovery.registerDevice(rokuDevice);
|
||||
|
||||
// MediaPlayerDevice will notify us any time the native device list changes.
|
||||
mediaPlayerDevice.init();
|
||||
SimpleServiceDiscovery.registerDevice(mediaPlayerDevice);
|
||||
|
||||
// Search for devices continuously
|
||||
SimpleServiceDiscovery.search(this._interval);
|
||||
|
||||
this._castMenuId = NativeWindow.contextmenus.add(
|
||||
Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
|
||||
this.filterCast,
|
||||
this.handleContextMenu.bind(this)
|
||||
);
|
||||
|
||||
GlobalEventDispatcher.registerListener(this, [
|
||||
"Casting:Play",
|
||||
"Casting:Pause",
|
||||
"Casting:Stop",
|
||||
]);
|
||||
|
||||
Services.obs.addObserver(this, "ssdp-service-found");
|
||||
Services.obs.addObserver(this, "ssdp-service-lost");
|
||||
Services.obs.addObserver(this, "application-background");
|
||||
Services.obs.addObserver(this, "application-foreground");
|
||||
|
||||
BrowserApp.deck.addEventListener("TabSelect", this, true);
|
||||
BrowserApp.deck.addEventListener("pageshow", this, true);
|
||||
BrowserApp.deck.addEventListener("playing", this, true);
|
||||
BrowserApp.deck.addEventListener("ended", this, true);
|
||||
BrowserApp.deck.addEventListener("MozAutoplayMediaBlocked", this, true);
|
||||
// Note that the XBL binding is untrusted
|
||||
BrowserApp.deck.addEventListener(
|
||||
"MozNoControlsVideoBindingAttached",
|
||||
this,
|
||||
true,
|
||||
true
|
||||
);
|
||||
},
|
||||
|
||||
_mirrorStarted: function(stopMirrorCallback) {
|
||||
this.stopMirrorCallback = stopMirrorCallback;
|
||||
NativeWindow.menu.update(this.mirrorStartMenuId, { visible: false });
|
||||
NativeWindow.menu.update(this.mirrorStopMenuId, { visible: true });
|
||||
},
|
||||
|
||||
serviceAdded: function(aService) {},
|
||||
|
||||
serviceLost: function(aService) {},
|
||||
|
||||
isCastingEnabled: function isCastingEnabled() {
|
||||
return Services.prefs.getBoolPref("browser.casting.enabled");
|
||||
},
|
||||
|
||||
onEvent: function(event, message, callback) {
|
||||
switch (event) {
|
||||
case "Casting:Play":
|
||||
if (this.session && this.session.remoteMedia.status == "paused") {
|
||||
this.session.remoteMedia.play();
|
||||
}
|
||||
break;
|
||||
case "Casting:Pause":
|
||||
if (this.session && this.session.remoteMedia.status == "started") {
|
||||
this.session.remoteMedia.pause();
|
||||
}
|
||||
break;
|
||||
case "Casting:Stop":
|
||||
if (this.session) {
|
||||
this.closeExternal();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "ssdp-service-found":
|
||||
this.serviceAdded(SimpleServiceDiscovery.findServiceForID(aData));
|
||||
break;
|
||||
case "ssdp-service-lost":
|
||||
this.serviceLost(SimpleServiceDiscovery.findServiceForID(aData));
|
||||
break;
|
||||
case "application-background":
|
||||
// Turn off polling while in the background
|
||||
this._interval = SimpleServiceDiscovery.search(0);
|
||||
SimpleServiceDiscovery.stopSearch();
|
||||
break;
|
||||
case "application-foreground":
|
||||
// Turn polling on when app comes back to foreground
|
||||
SimpleServiceDiscovery.search(this._interval);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "TabSelect": {
|
||||
let tab = BrowserApp.getTabForBrowser(aEvent.target);
|
||||
this._updatePageActionForTab(tab, aEvent);
|
||||
break;
|
||||
}
|
||||
case "pageshow": {
|
||||
let tab = BrowserApp.getTabForWindow(aEvent.originalTarget.defaultView);
|
||||
this._updatePageActionForTab(tab, aEvent);
|
||||
break;
|
||||
}
|
||||
case "playing":
|
||||
case "ended": {
|
||||
let video = aEvent.target;
|
||||
if (video instanceof HTMLVideoElement) {
|
||||
// If playing, send the <video>, but if ended we send nothing to shutdown the pageaction
|
||||
this._updatePageActionForVideo(
|
||||
aEvent.type === "playing" ? video : null
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "MozAutoplayMediaBlocked": {
|
||||
if (this._bound && this._bound.has(aEvent.target)) {
|
||||
aEvent.target.dispatchEvent(
|
||||
new aEvent.target.ownerGlobal.CustomEvent(
|
||||
"MozNoControlsBlockedVideo"
|
||||
)
|
||||
);
|
||||
} else {
|
||||
if (!this._blocked) {
|
||||
this._blocked = new WeakMap();
|
||||
}
|
||||
this._blocked.set(aEvent.target, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "MozNoControlsVideoBindingAttached": {
|
||||
if (!this._bound) {
|
||||
this._bound = new WeakMap();
|
||||
}
|
||||
|
||||
let video = this._findVideoFromEventTarget(aEvent.target);
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._bound.set(video, true);
|
||||
if (this._blocked && this._blocked.has(video)) {
|
||||
this._blocked.delete(video);
|
||||
video.dispatchEvent(
|
||||
new video.ownerGlobal.CustomEvent("MozNoControlsBlockedVideo")
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_findVideoFromEventTarget(aTarget) {
|
||||
if (
|
||||
typeof ShadowRoot !== "undefined" &&
|
||||
aTarget.parentNode instanceof ShadowRoot &&
|
||||
aTarget.parentNode.host instanceof HTMLVideoElement
|
||||
) {
|
||||
// aTarget is <div class="videocontrols"> inside UA Widget Shadow Root
|
||||
return aTarget.parentNode.host;
|
||||
}
|
||||
|
||||
if (aTarget instanceof HTMLVideoElement) {
|
||||
// aTarget is <video>.
|
||||
return aTarget;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
_sendEventToVideo: function _sendEventToVideo(aElement, aData) {
|
||||
let event = aElement.ownerDocument.createEvent("CustomEvent");
|
||||
event.initCustomEvent(
|
||||
"media-videoCasting",
|
||||
false,
|
||||
true,
|
||||
JSON.stringify(aData)
|
||||
);
|
||||
aElement.dispatchEvent(event);
|
||||
},
|
||||
|
||||
handleVideoBindingAttached: function handleVideoBindingAttached(
|
||||
aTab,
|
||||
aEvent
|
||||
) {
|
||||
// Let's figure out if we have everything needed to cast a video. The binding
|
||||
// defaults to |false| so we only need to send an event if |true|.
|
||||
let video = this._findVideoFromEventTarget(aEvent.target);
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SimpleServiceDiscovery.services.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getVideo(video, 0, 0, aBundle => {
|
||||
// Let the binding know casting is allowed
|
||||
if (aBundle) {
|
||||
this._sendEventToVideo(aBundle.element, { allow: true });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handleVideoBindingCast: function handleVideoBindingCast(aTab, aEvent) {
|
||||
// The binding wants to start a casting session
|
||||
let video = this._findVideoFromEventTarget(aEvent.target);
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close an existing session first. closeExternal has checks for an exsting
|
||||
// session and handles remote and video binding shutdown.
|
||||
this.closeExternal();
|
||||
|
||||
// Start the new session
|
||||
UITelemetry.addEvent("cast.1", "button", null);
|
||||
this.openExternal(video, 0, 0);
|
||||
},
|
||||
|
||||
makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) {
|
||||
return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
|
||||
},
|
||||
|
||||
allowableExtension: function(aURI, aExtensions) {
|
||||
return (
|
||||
aURI instanceof Ci.nsIURL && aExtensions.includes(aURI.fileExtension)
|
||||
);
|
||||
},
|
||||
|
||||
allowableMimeType: function(aType, aTypes) {
|
||||
return aTypes.includes(aType);
|
||||
},
|
||||
|
||||
// This method will look at the aElement (or try to find a video at aX, aY) that has
|
||||
// a castable source. If found, aCallback will be called with a JSON meta bundle. If
|
||||
// no castable source was found, aCallback is called with null.
|
||||
getVideo: function(aElement, aX, aY, aCallback) {
|
||||
let extensions = SimpleServiceDiscovery.getSupportedExtensions();
|
||||
let types = SimpleServiceDiscovery.getSupportedMimeTypes();
|
||||
|
||||
// Fast path: Is the given element a video element?
|
||||
if (aElement instanceof HTMLVideoElement) {
|
||||
// If we found a video element, no need to look further, even if no
|
||||
// castable video source is found.
|
||||
this._getVideo(aElement, types, extensions, aCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
// Maybe this is an overlay, with the video element under it.
|
||||
// Use the (x, y) location to guess at a <video> element.
|
||||
|
||||
// The context menu system will keep walking up the DOM giving us a chance
|
||||
// to find an element we match. When it hits <html> things can go BOOM.
|
||||
try {
|
||||
let elements = aElement.ownerDocument.querySelectorAll("video");
|
||||
for (let element of elements) {
|
||||
// Look for a video element contained in the overlay bounds
|
||||
let rect = element.getBoundingClientRect();
|
||||
if (
|
||||
aY >= rect.top &&
|
||||
aX >= rect.left &&
|
||||
aY <= rect.bottom &&
|
||||
aX <= rect.right
|
||||
) {
|
||||
// Once we find a <video> under the overlay, we check it and exit.
|
||||
this._getVideo(element, types, extensions, aCallback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
_getContentTypeForURI: function(aURI, aElement, aCallback) {
|
||||
let channel;
|
||||
try {
|
||||
let secFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
|
||||
if (aElement.crossOrigin) {
|
||||
secFlags = Ci.nsILoadInfo.SEC_REQUIRE_CORS_DATA_INHERITS;
|
||||
if (aElement.crossOrigin === "use-credentials") {
|
||||
secFlags |= Ci.nsILoadInfo.SEC_COOKIES_INCLUDE;
|
||||
}
|
||||
}
|
||||
channel = NetUtil.newChannel({
|
||||
uri: aURI,
|
||||
loadingNode: aElement,
|
||||
securityFlags: secFlags,
|
||||
contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_VIDEO,
|
||||
});
|
||||
} catch (e) {
|
||||
aCallback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let listener = {
|
||||
onStartRequest: function(request) {
|
||||
switch (channel.responseStatus) {
|
||||
case 301:
|
||||
case 302:
|
||||
case 303:
|
||||
request.cancel(0);
|
||||
let location = channel.getResponseHeader("Location");
|
||||
CastingApps._getContentTypeForURI(
|
||||
CastingApps.makeURI(location),
|
||||
aElement,
|
||||
aCallback
|
||||
);
|
||||
break;
|
||||
default:
|
||||
aCallback(channel.contentType);
|
||||
request.cancel(0);
|
||||
break;
|
||||
}
|
||||
},
|
||||
onStopRequest: function(request, statusCode) {},
|
||||
onDataAvailable: function(request, stream, offset, count) {},
|
||||
};
|
||||
|
||||
if (channel) {
|
||||
channel.asyncOpen(listener);
|
||||
} else {
|
||||
aCallback(null);
|
||||
}
|
||||
},
|
||||
|
||||
// Because this method uses a callback, make sure we return ASAP if we know
|
||||
// we have a castable video source.
|
||||
_getVideo: function(aElement, aTypes, aExtensions, aCallback) {
|
||||
// Keep a list of URIs we need for an async mimetype check
|
||||
let asyncURIs = [];
|
||||
|
||||
// Grab the poster attribute from the <video>
|
||||
let posterURL = aElement.poster;
|
||||
|
||||
// First, look to see if the <video> has a src attribute
|
||||
let sourceURL = aElement.src;
|
||||
|
||||
// If empty, try the currentSrc
|
||||
if (!sourceURL) {
|
||||
sourceURL = aElement.currentSrc;
|
||||
}
|
||||
|
||||
if (sourceURL) {
|
||||
// Use the file extension to guess the mime type
|
||||
let sourceURI = this.makeURI(
|
||||
sourceURL,
|
||||
null,
|
||||
this.makeURI(aElement.baseURI)
|
||||
);
|
||||
if (this.allowableExtension(sourceURI, aExtensions)) {
|
||||
aCallback({
|
||||
element: aElement,
|
||||
source: sourceURI.spec,
|
||||
poster: posterURL,
|
||||
sourceURI: sourceURI,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (aElement.type) {
|
||||
// Fast sync check
|
||||
if (this.allowableMimeType(aElement.type, aTypes)) {
|
||||
aCallback({
|
||||
element: aElement,
|
||||
source: sourceURI.spec,
|
||||
poster: posterURL,
|
||||
sourceURI: sourceURI,
|
||||
type: aElement.type,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Delay the async check until we sync scan all possible URIs
|
||||
asyncURIs.push(sourceURI);
|
||||
}
|
||||
|
||||
// Next, look to see if there is a <source> child element that meets
|
||||
// our needs
|
||||
let sourceNodes = aElement.getElementsByTagName("source");
|
||||
for (let sourceNode of sourceNodes) {
|
||||
let sourceURI = this.makeURI(
|
||||
sourceNode.src,
|
||||
null,
|
||||
this.makeURI(sourceNode.baseURI)
|
||||
);
|
||||
|
||||
// Using the type attribute is our ideal way to guess the mime type. Otherwise,
|
||||
// fallback to using the file extension to guess the mime type
|
||||
if (this.allowableExtension(sourceURI, aExtensions)) {
|
||||
aCallback({
|
||||
element: aElement,
|
||||
source: sourceURI.spec,
|
||||
poster: posterURL,
|
||||
sourceURI: sourceURI,
|
||||
type: sourceNode.type,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourceNode.type) {
|
||||
// Fast sync check
|
||||
if (this.allowableMimeType(sourceNode.type, aTypes)) {
|
||||
aCallback({
|
||||
element: aElement,
|
||||
source: sourceURI.spec,
|
||||
poster: posterURL,
|
||||
sourceURI: sourceURI,
|
||||
type: sourceNode.type,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Delay the async check until we sync scan all possible URIs
|
||||
asyncURIs.push(sourceURI);
|
||||
}
|
||||
|
||||
// Helper method that walks the array of possible URIs, fetching the mimetype as we go.
|
||||
// As soon as we find a good sourceURL, avoid firing the callback any further
|
||||
var _getContentTypeForURIs = aURIs => {
|
||||
// Do an async fetch to figure out the mimetype of the source video
|
||||
let sourceURI = aURIs.pop();
|
||||
this._getContentTypeForURI(sourceURI, aElement, aType => {
|
||||
if (this.allowableMimeType(aType, aTypes)) {
|
||||
// We found a supported mimetype.
|
||||
aCallback({
|
||||
element: aElement,
|
||||
source: sourceURI.spec,
|
||||
poster: posterURL,
|
||||
sourceURI: sourceURI,
|
||||
type: aType,
|
||||
});
|
||||
} else if (aURIs.length > 0) {
|
||||
// This URI was not a supported mimetype, so let's try the next, if we have more.
|
||||
_getContentTypeForURIs(aURIs);
|
||||
} else {
|
||||
// We were not able to find a supported mimetype.
|
||||
aCallback(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// If we didn't find a good URI directly, let's look using async methods.
|
||||
if (asyncURIs.length > 0) {
|
||||
_getContentTypeForURIs(asyncURIs);
|
||||
}
|
||||
},
|
||||
|
||||
// This code depends on handleVideoBindingAttached setting mozAllowCasting
|
||||
// so we can quickly figure out if the video is castable
|
||||
isVideoCastable: function(aElement, aX, aY) {
|
||||
// Use the flag set when the <video> binding was created as the check
|
||||
if (aElement instanceof HTMLVideoElement) {
|
||||
return aElement.mozAllowCasting;
|
||||
}
|
||||
|
||||
// This is called by the context menu system and the system will keep
|
||||
// walking up the DOM giving us a chance to find an element we match.
|
||||
// When it hits <html> things can go BOOM.
|
||||
try {
|
||||
// Maybe this is an overlay, with the video element under it
|
||||
// Use the (x, y) location to guess at a <video> element
|
||||
let elements = aElement.ownerDocument.querySelectorAll("video");
|
||||
for (let element of elements) {
|
||||
// Look for a video element contained in the overlay bounds
|
||||
let rect = element.getBoundingClientRect();
|
||||
if (
|
||||
aY >= rect.top &&
|
||||
aX >= rect.left &&
|
||||
aY <= rect.bottom &&
|
||||
aX <= rect.right
|
||||
) {
|
||||
// Use the flag set when the <video> binding was created as the check
|
||||
return element.mozAllowCasting;
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
filterCast: {
|
||||
matches: function(aElement, aX, aY) {
|
||||
// This behavior matches the pageaction: As long as a video is castable,
|
||||
// we can cast it, even if it's already being cast to a device.
|
||||
if (SimpleServiceDiscovery.services.length == 0) {
|
||||
return false;
|
||||
}
|
||||
return CastingApps.isVideoCastable(aElement, aX, aY);
|
||||
},
|
||||
},
|
||||
|
||||
pageAction: {
|
||||
click: function() {
|
||||
// Since this is a pageaction, we use the selected browser
|
||||
let browser = BrowserApp.selectedBrowser;
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for a castable <video> that is playing, and start casting it
|
||||
let videos = browser.contentDocument.querySelectorAll("video");
|
||||
for (let video of videos) {
|
||||
if (!video.paused && video.mozAllowCasting) {
|
||||
UITelemetry.addEvent("cast.1", "pageaction", null);
|
||||
CastingApps.openExternal(video, 0, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
_findCastableVideo: function _findCastableVideo(aBrowser) {
|
||||
if (!aBrowser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Scan for a <video> being actively cast. Also look for a castable <video>
|
||||
// on the page.
|
||||
let castableVideo = null;
|
||||
let videos = aBrowser.contentDocument.querySelectorAll("video");
|
||||
for (let video of videos) {
|
||||
if (video.mozIsCasting) {
|
||||
// This <video> is cast-active. Break out of loop.
|
||||
return video;
|
||||
}
|
||||
|
||||
if (!video.paused && video.mozAllowCasting) {
|
||||
// This <video> is cast-ready. Keep looking so cast-active could be found.
|
||||
castableVideo = video;
|
||||
}
|
||||
}
|
||||
|
||||
// Could be null
|
||||
return castableVideo;
|
||||
},
|
||||
|
||||
_updatePageActionForTab: function _updatePageActionForTab(aTab, aEvent) {
|
||||
// We only care about events on the selected tab
|
||||
if (aTab != BrowserApp.selectedTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the page action, scanning for a castable <video>
|
||||
this._updatePageAction();
|
||||
},
|
||||
|
||||
_updatePageActionForVideo: function _updatePageActionForVideo(aVideo) {
|
||||
this._updatePageAction(aVideo);
|
||||
},
|
||||
|
||||
_updatePageAction: function _updatePageAction(aVideo) {
|
||||
// Remove any exising pageaction first, in case state changes or we don't have
|
||||
// a castable video
|
||||
if (this.pageAction.id) {
|
||||
PageActions.remove(this.pageAction.id);
|
||||
delete this.pageAction.id;
|
||||
}
|
||||
|
||||
if (!aVideo) {
|
||||
aVideo = this._findCastableVideo(BrowserApp.selectedBrowser);
|
||||
if (!aVideo) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We only show pageactions if the <video> is from the selected tab
|
||||
if (
|
||||
BrowserApp.selectedTab !=
|
||||
BrowserApp.getTabForWindow(aVideo.ownerGlobal.top)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We check for two state here:
|
||||
// 1. The video is actively being cast
|
||||
// 2. The video is allowed to be cast and is currently playing
|
||||
// Both states have the same action: Show the cast page action
|
||||
if (aVideo.mozIsCasting) {
|
||||
this.pageAction.id = PageActions.add({
|
||||
title: Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
|
||||
icon: "drawable://casting_active",
|
||||
clickCallback: this.pageAction.click,
|
||||
important: true,
|
||||
useTint: false,
|
||||
});
|
||||
} else if (aVideo.mozAllowCasting) {
|
||||
this.pageAction.id = PageActions.add({
|
||||
title: Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
|
||||
icon: "drawable://casting",
|
||||
clickCallback: this.pageAction.click,
|
||||
important: true,
|
||||
useTint: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
prompt: function(aWindow, aCallback, aFilterFunc) {
|
||||
let items = [];
|
||||
let filteredServices = [];
|
||||
SimpleServiceDiscovery.services.forEach(function(aService) {
|
||||
let item = {
|
||||
label: aService.friendlyName,
|
||||
selected: false,
|
||||
};
|
||||
if (!aFilterFunc || aFilterFunc(aService)) {
|
||||
filteredServices.push(aService);
|
||||
items.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
if (items.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let prompt = new Prompt({
|
||||
window: aWindow,
|
||||
title: Strings.browser.GetStringFromName("casting.sendToDevice"),
|
||||
})
|
||||
.setSingleChoiceItems(items)
|
||||
.show(function(data) {
|
||||
let selected = data.button;
|
||||
let service = selected == -1 ? null : filteredServices[selected];
|
||||
if (aCallback) {
|
||||
aCallback(service);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handleContextMenu: function(aElement, aX, aY) {
|
||||
UITelemetry.addEvent("action.1", "contextmenu", null, "web_cast");
|
||||
UITelemetry.addEvent("cast.1", "contextmenu", null);
|
||||
this.openExternal(aElement, aX, aY);
|
||||
},
|
||||
|
||||
openExternal: function(aElement, aX, aY) {
|
||||
// Start a second screen media service
|
||||
this.getVideo(aElement, aX, aY, this._openExternal.bind(this));
|
||||
},
|
||||
|
||||
_openExternal: function(aVideo) {
|
||||
if (!aVideo) {
|
||||
return;
|
||||
}
|
||||
|
||||
function filterFunc(aService) {
|
||||
return (
|
||||
this.allowableExtension(aVideo.sourceURI, aService.extensions) ||
|
||||
this.allowableMimeType(aVideo.type, aService.types)
|
||||
);
|
||||
}
|
||||
|
||||
this.prompt(
|
||||
aVideo.element.ownerGlobal,
|
||||
aService => {
|
||||
if (!aService) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we have a player app for the given service
|
||||
let app = SimpleServiceDiscovery.findAppForService(aService);
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aVideo.element) {
|
||||
aVideo.title = aVideo.element.ownerGlobal.top.document.title;
|
||||
|
||||
// If the video is currently playing on the device, pause it
|
||||
if (!aVideo.element.paused) {
|
||||
aVideo.element.pause();
|
||||
}
|
||||
}
|
||||
|
||||
app.stop(() => {
|
||||
app.start(aStarted => {
|
||||
if (!aStarted) {
|
||||
dump("CastingApps: Unable to start app");
|
||||
return;
|
||||
}
|
||||
|
||||
app.remoteMedia(aRemoteMedia => {
|
||||
if (!aRemoteMedia) {
|
||||
dump("CastingApps: Failed to create remotemedia");
|
||||
return;
|
||||
}
|
||||
|
||||
this.session = {
|
||||
service: aService,
|
||||
app: app,
|
||||
remoteMedia: aRemoteMedia,
|
||||
data: {
|
||||
title: aVideo.title,
|
||||
source: aVideo.source,
|
||||
poster: aVideo.poster,
|
||||
},
|
||||
videoRef: Cu.getWeakReference(aVideo.element),
|
||||
};
|
||||
}, this);
|
||||
});
|
||||
});
|
||||
},
|
||||
filterFunc.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
closeExternal: function() {
|
||||
if (!this.session) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.session.remoteMedia.shutdown();
|
||||
this._shutdown();
|
||||
},
|
||||
|
||||
_shutdown: function() {
|
||||
if (!this.session) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.session.app.stop();
|
||||
let video = this.session.videoRef.get();
|
||||
if (video) {
|
||||
this._sendEventToVideo(video, { active: false });
|
||||
this._updatePageAction();
|
||||
}
|
||||
|
||||
delete this.session;
|
||||
},
|
||||
|
||||
// RemoteMedia callback API methods
|
||||
onRemoteMediaStart: function(aRemoteMedia) {
|
||||
if (!this.session) {
|
||||
return;
|
||||
}
|
||||
|
||||
aRemoteMedia.load(this.session.data);
|
||||
GlobalEventDispatcher.sendRequest({
|
||||
type: "Casting:Started",
|
||||
device: this.session.service.friendlyName,
|
||||
});
|
||||
|
||||
let video = this.session.videoRef.get();
|
||||
if (video) {
|
||||
this._sendEventToVideo(video, { active: true });
|
||||
this._updatePageAction(video);
|
||||
}
|
||||
},
|
||||
|
||||
onRemoteMediaStop: function(aRemoteMedia) {
|
||||
GlobalEventDispatcher.sendRequest({ type: "Casting:Stopped" });
|
||||
this._shutdown();
|
||||
},
|
||||
|
||||
onRemoteMediaStatus: function(aRemoteMedia) {
|
||||
if (!this.session) {
|
||||
return;
|
||||
}
|
||||
|
||||
let status = aRemoteMedia.status;
|
||||
switch (status) {
|
||||
case "started":
|
||||
GlobalEventDispatcher.sendRequest({ type: "Casting:Playing" });
|
||||
break;
|
||||
case "paused":
|
||||
GlobalEventDispatcher.sendRequest({ type: "Casting:Paused" });
|
||||
break;
|
||||
case "completed":
|
||||
this.closeExternal();
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
@ -1,145 +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/. */
|
||||
"use strict";
|
||||
|
||||
var ConsoleAPI = {
|
||||
observe: function observe(aMessage, aTopic, aData) {
|
||||
aMessage = aMessage.wrappedJSObject;
|
||||
|
||||
let mappedArguments = Array.from(
|
||||
aMessage.arguments,
|
||||
this.formatResult,
|
||||
this
|
||||
);
|
||||
let joinedArguments = mappedArguments.join(" ");
|
||||
|
||||
if (aMessage.level == "error" || aMessage.level == "warn") {
|
||||
let flag =
|
||||
aMessage.level == "error"
|
||||
? Ci.nsIScriptError.errorFlag
|
||||
: Ci.nsIScriptError.warningFlag;
|
||||
let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(
|
||||
Ci.nsIScriptError
|
||||
);
|
||||
consoleMsg.init(
|
||||
joinedArguments,
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
flag,
|
||||
"content javascript"
|
||||
);
|
||||
Services.console.logMessage(consoleMsg);
|
||||
} else if (aMessage.level == "trace") {
|
||||
let bundle = Services.strings.createBundle(
|
||||
"chrome://browser/locale/browser.properties"
|
||||
);
|
||||
let args = aMessage.arguments;
|
||||
let filename = this.abbreviateSourceURL(args[0].filename);
|
||||
let functionName =
|
||||
args[0].functionName ||
|
||||
bundle.GetStringFromName("stacktrace.anonymousFunction");
|
||||
let lineNumber = args[0].lineNumber;
|
||||
|
||||
let body = bundle.formatStringFromName("stacktrace.outputMessage", [
|
||||
filename,
|
||||
functionName,
|
||||
lineNumber,
|
||||
]);
|
||||
body += "\n";
|
||||
args.forEach(function(aFrame) {
|
||||
let functionName =
|
||||
aFrame.functionName ||
|
||||
bundle.GetStringFromName("stacktrace.anonymousFunction");
|
||||
body +=
|
||||
" " +
|
||||
aFrame.filename +
|
||||
" :: " +
|
||||
functionName +
|
||||
" :: " +
|
||||
aFrame.lineNumber +
|
||||
"\n";
|
||||
});
|
||||
|
||||
Services.console.logStringMessage(body);
|
||||
} else if (aMessage.level == "time" && aMessage.arguments) {
|
||||
let bundle = Services.strings.createBundle(
|
||||
"chrome://browser/locale/browser.properties"
|
||||
);
|
||||
let body = bundle.formatStringFromName("timer.start", [
|
||||
aMessage.arguments.name,
|
||||
]);
|
||||
Services.console.logStringMessage(body);
|
||||
} else if (aMessage.level == "timeEnd" && aMessage.arguments) {
|
||||
let bundle = Services.strings.createBundle(
|
||||
"chrome://browser/locale/browser.properties"
|
||||
);
|
||||
let body = bundle.formatStringFromName("timer.end", [
|
||||
aMessage.arguments.name,
|
||||
aMessage.arguments.duration,
|
||||
]);
|
||||
Services.console.logStringMessage(body);
|
||||
} else if (
|
||||
["group", "groupCollapsed", "groupEnd"].includes(aMessage.level)
|
||||
) {
|
||||
// Do nothing yet
|
||||
} else {
|
||||
Services.console.logStringMessage(joinedArguments);
|
||||
}
|
||||
},
|
||||
|
||||
getResultType: function getResultType(aResult) {
|
||||
let type = aResult === null ? "null" : typeof aResult;
|
||||
if (type == "object" && aResult.constructor && aResult.constructor.name) {
|
||||
type = aResult.constructor.name;
|
||||
}
|
||||
return type.toLowerCase();
|
||||
},
|
||||
|
||||
formatResult: function formatResult(aResult) {
|
||||
let output = "";
|
||||
let type = this.getResultType(aResult);
|
||||
switch (type) {
|
||||
case "string":
|
||||
case "boolean":
|
||||
case "date":
|
||||
case "error":
|
||||
case "number":
|
||||
case "regexp":
|
||||
output = aResult.toString();
|
||||
break;
|
||||
case "null":
|
||||
case "undefined":
|
||||
output = type;
|
||||
break;
|
||||
default:
|
||||
output = aResult.toString();
|
||||
break;
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
abbreviateSourceURL: function abbreviateSourceURL(aSourceURL) {
|
||||
// Remove any query parameters.
|
||||
let hookIndex = aSourceURL.indexOf("?");
|
||||
if (hookIndex > -1) {
|
||||
aSourceURL = aSourceURL.substring(0, hookIndex);
|
||||
}
|
||||
|
||||
// Remove a trailing "/".
|
||||
if (aSourceURL[aSourceURL.length - 1] == "/") {
|
||||
aSourceURL = aSourceURL.substring(0, aSourceURL.length - 1);
|
||||
}
|
||||
|
||||
// Remove all but the last path component.
|
||||
let slashIndex = aSourceURL.lastIndexOf("/");
|
||||
if (slashIndex > -1) {
|
||||
aSourceURL = aSourceURL.substring(slashIndex + 1);
|
||||
}
|
||||
|
||||
return aSourceURL;
|
||||
},
|
||||
};
|
@ -1,73 +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/. */
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"ConsoleAPI",
|
||||
"resource://gre/modules/Console.jsm"
|
||||
);
|
||||
|
||||
/*
|
||||
* Collection of methods and features specific to using a GeckoView instance.
|
||||
* The code is isolated from browser.js for code size and performance reasons.
|
||||
*/
|
||||
var EmbedRT = {
|
||||
_scopes: {},
|
||||
|
||||
onEvent: function(event, data, callback) {
|
||||
switch (event) {
|
||||
case "GeckoView:ImportScript":
|
||||
this.importScript(data.scriptURL);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Loads a script file into a sandbox and calls an optional load function
|
||||
*/
|
||||
importScript: function(scriptURL) {
|
||||
if (scriptURL in this._scopes) {
|
||||
return;
|
||||
}
|
||||
|
||||
let principal = Cc["@mozilla.org/systemprincipal;1"].createInstance(
|
||||
Ci.nsIPrincipal
|
||||
);
|
||||
|
||||
let sandbox = new Cu.Sandbox(principal, {
|
||||
sandboxName: scriptURL,
|
||||
wantGlobalProperties: ["indexedDB"],
|
||||
});
|
||||
|
||||
sandbox.console = new ConsoleAPI({ consoleID: "script/" + scriptURL });
|
||||
|
||||
// As we don't want our caller to control the JS version used for the
|
||||
// script file, we run loadSubScript within the context of the
|
||||
// sandbox with the latest JS version set explicitly.
|
||||
sandbox.__SCRIPT_URI_SPEC__ = scriptURL;
|
||||
Cu.evalInSandbox(
|
||||
"Components.classes['@mozilla.org/moz/jssubscript-loader;1'].createInstance(Components.interfaces.mozIJSSubScriptLoader).loadSubScript(__SCRIPT_URI_SPEC__);",
|
||||
sandbox,
|
||||
"ECMAv5"
|
||||
);
|
||||
|
||||
this._scopes[scriptURL] = sandbox;
|
||||
|
||||
if ("load" in sandbox) {
|
||||
let params = {
|
||||
window: window,
|
||||
resourceURI: scriptURL,
|
||||
};
|
||||
|
||||
try {
|
||||
sandbox.load(params);
|
||||
} catch (e) {
|
||||
dump(
|
||||
"Exception calling 'load' method in script: " + scriptURL + "\n" + e
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
@ -1,158 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"ExtensionData",
|
||||
"resource://gre/modules/Extension.jsm"
|
||||
);
|
||||
|
||||
var ExtensionPermissions = {
|
||||
// id -> object containing update details (see applyUpdate() )
|
||||
updates: new Map(),
|
||||
|
||||
// Prepare the strings needed for a permission notification.
|
||||
_prepareStrings(info) {
|
||||
let appName = Strings.brand.GetStringFromName("brandShortName");
|
||||
let info2 = Object.assign({ appName }, info);
|
||||
let strings = ExtensionData.formatPermissionStrings(info2, Strings.browser);
|
||||
|
||||
// We dump the main body of the dialog into a big android
|
||||
// TextView. Build a big string with the full contents here.
|
||||
let message = "";
|
||||
if (strings.msgs.length > 0) {
|
||||
message = [
|
||||
strings.listIntro,
|
||||
...strings.msgs.map(s => `\u2022 ${s}`),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
return {
|
||||
header: strings.header.replace("<>", info.addonName),
|
||||
message,
|
||||
acceptText: strings.acceptText,
|
||||
cancelText: strings.cancelText,
|
||||
};
|
||||
},
|
||||
|
||||
// Prepare an icon for a permission notification
|
||||
_prepareIcon(iconURL) {
|
||||
// We can render pngs with ResourceDrawableUtils
|
||||
if (iconURL.endsWith(".png")) {
|
||||
return iconURL;
|
||||
}
|
||||
|
||||
// If we can't render an icon, show the default
|
||||
return "DEFAULT";
|
||||
},
|
||||
|
||||
async observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "webextension-permission-prompt": {
|
||||
let { target, info } = subject.wrappedJSObject;
|
||||
let stringInfo = Object.assign({ addonName: info.addon.name }, info);
|
||||
let details = this._prepareStrings(stringInfo);
|
||||
details.icon = this._prepareIcon(info.icon);
|
||||
details.type = "Extension:PermissionPrompt";
|
||||
let accepted = await EventDispatcher.instance.sendRequestForResult(
|
||||
details
|
||||
);
|
||||
|
||||
if (accepted) {
|
||||
info.resolve();
|
||||
} else {
|
||||
info.reject();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "webextension-update-permissions":
|
||||
let info = subject.wrappedJSObject;
|
||||
let { addon, resolve, reject } = info;
|
||||
let stringInfo = Object.assign(
|
||||
{
|
||||
type: "update",
|
||||
addonName: addon.name,
|
||||
},
|
||||
info
|
||||
);
|
||||
|
||||
let details = this._prepareStrings(stringInfo);
|
||||
|
||||
// If there are no promptable permissions, just apply the update
|
||||
if (details.message.length == 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store all the details about the update until the user chooses to
|
||||
// look at update, at which point we will pick up in this.applyUpdate()
|
||||
details.icon = this._prepareIcon(addon.iconURL || "dummy.svg");
|
||||
|
||||
let first = this.updates.size == 0;
|
||||
this.updates.set(addon.id, { details, resolve, reject });
|
||||
|
||||
if (first) {
|
||||
EventDispatcher.instance.sendRequest({
|
||||
type: "Extension:ShowUpdateIcon",
|
||||
value: true,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "webextension-optional-permission-prompt": {
|
||||
let info = subject.wrappedJSObject;
|
||||
let { name, resolve } = info;
|
||||
let stringInfo = Object.assign(
|
||||
{
|
||||
type: "optional",
|
||||
addonName: name,
|
||||
},
|
||||
info
|
||||
);
|
||||
|
||||
let details = this._prepareStrings(stringInfo);
|
||||
|
||||
// If there are no promptable permissions, just apply the update
|
||||
if (details.message.length == 0) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store all the details about the update until the user chooses to
|
||||
// look at update, at which point we will pick up in this.applyUpdate()
|
||||
details.icon = this._prepareIcon(info.icon || "dummy.svg");
|
||||
|
||||
details.type = "Extension:PermissionPrompt";
|
||||
let accepted = await EventDispatcher.instance.sendRequestForResult(
|
||||
details
|
||||
);
|
||||
resolve(accepted);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async applyUpdate(id) {
|
||||
if (!this.updates.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let update = this.updates.get(id);
|
||||
this.updates.delete(id);
|
||||
if (this.updates.size == 0) {
|
||||
EventDispatcher.instance.sendRequest({
|
||||
type: "Extension:ShowUpdateIcon",
|
||||
value: false,
|
||||
});
|
||||
}
|
||||
|
||||
let { details } = update;
|
||||
details.type = "Extension:PermissionPrompt";
|
||||
|
||||
let accepted = await EventDispatcher.instance.sendRequestForResult(details);
|
||||
if (accepted) {
|
||||
update.resolve();
|
||||
} else {
|
||||
update.reject();
|
||||
}
|
||||
},
|
||||
};
|
@ -1,147 +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/. */
|
||||
"use strict";
|
||||
|
||||
var FeedHandler = {
|
||||
PREF_CONTENTHANDLERS_BRANCH: "browser.contentHandlers.types.",
|
||||
TYPE_MAYBE_FEED: "application/vnd.mozilla.maybe.feed",
|
||||
|
||||
_contentTypes: null,
|
||||
|
||||
getContentHandlers: function fh_getContentHandlers(contentType) {
|
||||
if (!this._contentTypes) {
|
||||
this.loadContentHandlers();
|
||||
}
|
||||
|
||||
if (!(contentType in this._contentTypes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this._contentTypes[contentType];
|
||||
},
|
||||
|
||||
loadContentHandlers: function fh_loadContentHandlers() {
|
||||
this._contentTypes = {};
|
||||
|
||||
let kids = Services.prefs
|
||||
.getBranch(this.PREF_CONTENTHANDLERS_BRANCH)
|
||||
.getChildList("");
|
||||
|
||||
// First get the numbers of the providers by getting all ###.uri prefs
|
||||
let nums = [];
|
||||
for (let i = 0; i < kids.length; i++) {
|
||||
let match = /^(\d+)\.uri$/.exec(kids[i]);
|
||||
if (!match) {
|
||||
continue;
|
||||
} else {
|
||||
nums.push(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort them, to get them back in order
|
||||
nums.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
// Now register them
|
||||
for (let i = 0; i < nums.length; i++) {
|
||||
let branch = Services.prefs.getBranch(
|
||||
this.PREF_CONTENTHANDLERS_BRANCH + nums[i] + "."
|
||||
);
|
||||
let vals = branch.getChildList("");
|
||||
if (vals.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let type = branch.getCharPref("type");
|
||||
let uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data;
|
||||
let title = branch.getComplexValue("title", Ci.nsIPrefLocalizedString)
|
||||
.data;
|
||||
|
||||
if (!(type in this._contentTypes)) {
|
||||
this._contentTypes[type] = [];
|
||||
}
|
||||
this._contentTypes[type].push({
|
||||
contentType: type,
|
||||
uri: uri,
|
||||
name: title,
|
||||
});
|
||||
} catch (ex) {}
|
||||
}
|
||||
},
|
||||
|
||||
onEvent: function fh_onEvent(event, args, callback) {
|
||||
if (event === "Feeds:Subscribe") {
|
||||
let tab = BrowserApp.getTabForId(args.tabId);
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
let browser = tab.browser;
|
||||
let feeds = browser.feeds;
|
||||
if (feeds == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First, let's decide on which feed to subscribe
|
||||
let feedIndex = -1;
|
||||
if (feeds.length > 1) {
|
||||
let p = new Prompt({
|
||||
window: browser.contentWindow,
|
||||
title: Strings.browser.GetStringFromName("feedHandler.chooseFeed"),
|
||||
})
|
||||
.setSingleChoiceItems(
|
||||
feeds.map(function(feed) {
|
||||
return { label: feed.title || feed.href };
|
||||
})
|
||||
)
|
||||
.show(data => {
|
||||
feedIndex = data.button;
|
||||
if (feedIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadFeed(feeds[feedIndex], browser);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadFeed(feeds[0], browser);
|
||||
}
|
||||
},
|
||||
|
||||
loadFeed: function fh_loadFeed(aFeed, aBrowser) {
|
||||
let feedURL = aFeed.href;
|
||||
|
||||
// Next, we decide on which service to send the feed
|
||||
let handlers = this.getContentHandlers(this.TYPE_MAYBE_FEED);
|
||||
if (handlers.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// JSON for Prompt
|
||||
let p = new Prompt({
|
||||
window: aBrowser.contentWindow,
|
||||
title: Strings.browser.GetStringFromName("feedHandler.subscribeWith"),
|
||||
})
|
||||
.setSingleChoiceItems(
|
||||
handlers.map(function(handler) {
|
||||
return { label: handler.name };
|
||||
})
|
||||
)
|
||||
.show(function(data) {
|
||||
if (data.button == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge the handler URL and the feed URL
|
||||
let readerURL = handlers[data.button].uri;
|
||||
readerURL = readerURL.replace(/%s/gi, encodeURIComponent(feedURL));
|
||||
|
||||
// Open the resultant URL in a new tab
|
||||
BrowserApp.addTab(readerURL, { parentId: BrowserApp.selectedTab.id });
|
||||
});
|
||||
},
|
||||
};
|
@ -1,69 +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/. */
|
||||
"use strict";
|
||||
|
||||
var Feedback = {
|
||||
get _feedbackURL() {
|
||||
delete this._feedbackURL;
|
||||
return (this._feedbackURL = Services.urlFormatter.formatURLPref(
|
||||
"app.feedbackURL"
|
||||
));
|
||||
},
|
||||
|
||||
onEvent: function(event, data, callback) {
|
||||
if (event !== "Feedback:Show") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't prompt for feedback in distribution builds.
|
||||
try {
|
||||
Services.prefs.getCharPref("distribution.id");
|
||||
return;
|
||||
} catch (e) {}
|
||||
|
||||
let url = this._feedbackURL;
|
||||
let browser = BrowserApp.selectOrAddTab(url, {
|
||||
parentId: BrowserApp.selectedTab.id,
|
||||
}).browser;
|
||||
|
||||
browser.addEventListener("FeedbackClose", this, false, true);
|
||||
browser.addEventListener("FeedbackMaybeLater", this, false, true);
|
||||
|
||||
// Dispatch a custom event to the page content when feedback is prompted by the browser.
|
||||
// This will be used by the page to determine it's being loaded directly by the browser,
|
||||
// instead of by the user visiting the page, e.g. through browser history.
|
||||
function loadListener(event) {
|
||||
browser.removeEventListener("DOMContentLoaded", loadListener);
|
||||
browser.contentDocument.dispatchEvent(
|
||||
new CustomEvent("FeedbackPrompted")
|
||||
);
|
||||
}
|
||||
browser.addEventListener("DOMContentLoaded", loadListener);
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
if (!this._isAllowed(event.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case "FeedbackClose":
|
||||
// Do nothing.
|
||||
break;
|
||||
|
||||
case "FeedbackMaybeLater":
|
||||
GlobalEventDispatcher.sendRequest({ type: "Feedback:MaybeLater" });
|
||||
break;
|
||||
}
|
||||
|
||||
let win = event.target.ownerGlobal.top;
|
||||
BrowserApp.closeTab(BrowserApp.getTabForWindow(win));
|
||||
},
|
||||
|
||||
_isAllowed: function(node) {
|
||||
let uri = node.ownerDocument.documentURIObject;
|
||||
let feedbackURI = Services.io.newURI(this._feedbackURL);
|
||||
return uri.prePath === feedbackURI.prePath;
|
||||
},
|
||||
};
|
@ -1,231 +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/. */
|
||||
"use strict";
|
||||
|
||||
var FindHelper = {
|
||||
_finder: null,
|
||||
_targetTab: null,
|
||||
_initialViewport: null,
|
||||
_viewportChanged: false,
|
||||
_result: null,
|
||||
|
||||
// Start of nsIObserver implementation.
|
||||
|
||||
onEvent: function(event, data, callback) {
|
||||
switch (event) {
|
||||
case "FindInPage:Opened": {
|
||||
this._findOpened();
|
||||
break;
|
||||
}
|
||||
|
||||
case "FindInPage:Closed": {
|
||||
this._uninit();
|
||||
this._findClosed();
|
||||
break;
|
||||
}
|
||||
|
||||
case "Tab:Selected": {
|
||||
// Allow for page switching.
|
||||
this._uninit();
|
||||
break;
|
||||
}
|
||||
|
||||
case "FindInPage:Find": {
|
||||
this.doFind(data.searchString);
|
||||
break;
|
||||
}
|
||||
|
||||
case "FindInPage:Next": {
|
||||
this.findAgain(data.searchString, false);
|
||||
break;
|
||||
}
|
||||
|
||||
case "FindInPage:Prev": {
|
||||
this.findAgain(data.searchString, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When the FindInPageBar opens/ becomes visible, it's time to:
|
||||
* 1. Add listeners for other message types sent from the FindInPageBar
|
||||
* 2. initialize the Finder instance, if necessary.
|
||||
*/
|
||||
_findOpened: function() {
|
||||
GlobalEventDispatcher.registerListener(this, [
|
||||
"FindInPage:Find",
|
||||
"FindInPage:Next",
|
||||
"FindInPage:Prev",
|
||||
]);
|
||||
|
||||
// Initialize the finder component for the current page by performing a fake find.
|
||||
this._init();
|
||||
this._finder.requestMatchesCount("");
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the Finder instance from the active tabs' browser and start tracking
|
||||
* the active viewport.
|
||||
*/
|
||||
_init: function() {
|
||||
// If there's no find in progress, start one.
|
||||
if (this._finder) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._targetTab = BrowserApp.selectedTab;
|
||||
try {
|
||||
this._finder = this._targetTab.browser.finder;
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
"FindHelper: " +
|
||||
e +
|
||||
"\n" +
|
||||
"JS stack: \n" +
|
||||
(e.stack || Components.stack.formattedStack)
|
||||
);
|
||||
}
|
||||
|
||||
this._finder.addResultListener(this);
|
||||
this._initialViewport = JSON.stringify(this._targetTab.getViewport());
|
||||
this._viewportChanged = false;
|
||||
|
||||
WindowEventDispatcher.registerListener(this, ["Tab:Selected"]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Detach from the Finder instance (so stop listening for messages) and stop
|
||||
* tracking the active viewport.
|
||||
*/
|
||||
_uninit: function() {
|
||||
// If there's no find in progress, there's nothing to clean up.
|
||||
if (!this._finder) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._finder.removeSelection();
|
||||
this._finder.removeResultListener(this);
|
||||
this._finder = null;
|
||||
this._targetTab = null;
|
||||
this._initialViewport = null;
|
||||
this._viewportChanged = false;
|
||||
|
||||
WindowEventDispatcher.unregisterListener(this, ["Tab:Selected"]);
|
||||
},
|
||||
|
||||
/**
|
||||
* When the FindInPageBar closes, it's time to stop listening for its messages.
|
||||
*/
|
||||
_findClosed: function() {
|
||||
GlobalEventDispatcher.unregisterListener(this, [
|
||||
"FindInPage:Find",
|
||||
"FindInPage:Next",
|
||||
"FindInPage:Prev",
|
||||
]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start an asynchronous find-in-page operation, using the current Finder
|
||||
* instance and request to count the amount of matches.
|
||||
* If no Finder instance is currently active, we'll lazily initialize it here.
|
||||
*
|
||||
* @param {String} searchString Word to search for in the current document
|
||||
* @return {Object} Echo of the current find action
|
||||
*/
|
||||
doFind: function(searchString) {
|
||||
if (!this._finder) {
|
||||
this._init();
|
||||
}
|
||||
|
||||
this._finder.fastFind(searchString, false);
|
||||
return { searchString, findBackwards: false };
|
||||
},
|
||||
|
||||
/**
|
||||
* Restart the same find-in-page operation as before via `doFind()`. If we
|
||||
* haven't called `doFind()`, we simply kick off a regular find.
|
||||
*
|
||||
* @param {String} searchString Word to search for in the current document
|
||||
* @param {Boolean} findBackwards Direction to search in
|
||||
* @return {Object} Echo of the current find action
|
||||
*/
|
||||
findAgain: function(searchString, findBackwards) {
|
||||
// This always happens if the user taps next/previous after re-opening the
|
||||
// search bar, and not only forces _init() but also an initial fastFind(STRING)
|
||||
// before any findAgain(DIRECTION).
|
||||
if (!this._finder) {
|
||||
return this.doFind(searchString);
|
||||
}
|
||||
|
||||
this._finder.findAgain(searchString, findBackwards, false, false);
|
||||
return { searchString, findBackwards };
|
||||
},
|
||||
|
||||
// Start of Finder.jsm listener implementation.
|
||||
|
||||
/**
|
||||
* Pass along the count results to FindInPageBar for display. The result that
|
||||
* is sent to the FindInPageBar is augmented with the current find-in-page count
|
||||
* limit.
|
||||
*
|
||||
* @param {Object} result Result coming from the Finder instance that contains
|
||||
* the following properties:
|
||||
* - {Number} total The total amount of matches found
|
||||
* - {Number} current The index of current found range
|
||||
* in the document
|
||||
*/
|
||||
onMatchesCountResult: function(result) {
|
||||
this._result = result;
|
||||
|
||||
GlobalEventDispatcher.sendRequest(
|
||||
Object.assign(
|
||||
{
|
||||
type: "FindInPage:MatchesCountResult",
|
||||
},
|
||||
this._result
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* When a find-in-page action finishes, this method is invoked. This is mainly
|
||||
* used at the moment to detect if the current viewport has changed, which might
|
||||
* be indicated by not finding a string in the current page.
|
||||
*
|
||||
* @param {Object} aData A dictionary, representing the find result, which
|
||||
* contains the following properties:
|
||||
* - {String} searchString Word that was searched for
|
||||
* in the current document
|
||||
* - {Number} result One of the following
|
||||
* Ci.nsITypeAheadFind.* result
|
||||
* indicators: FIND_FOUND,
|
||||
* FIND_NOTFOUND, FIND_WRAPPED,
|
||||
* FIND_PENDING
|
||||
* - {Boolean} findBackwards Whether the search direction
|
||||
* was backwards
|
||||
* - {Boolean} findAgain Whether the previous search
|
||||
* was repeated
|
||||
* - {Boolean} drawOutline Whether we may (re-)draw the
|
||||
* outline of a hyperlink
|
||||
* - {Boolean} linksOnly Whether links-only mode was
|
||||
* active
|
||||
*/
|
||||
onFindResult: function(aData) {
|
||||
if (aData.result == Ci.nsITypeAheadFind.FIND_NOTFOUND) {
|
||||
if (this._viewportChanged) {
|
||||
if (this._targetTab != BrowserApp.selectedTab) {
|
||||
// this should never happen
|
||||
Cu.reportError("Warning: selected tab changed during find!");
|
||||
// fall through and restore viewport on the initial tab anyway
|
||||
}
|
||||
this._targetTab.sendViewportUpdate();
|
||||
}
|
||||
} else {
|
||||
// Disabled until bug 1014113 is fixed
|
||||
// ZoomHelper.zoomToRect(aData.rect);
|
||||
this._viewportChanged = true;
|
||||
}
|
||||
},
|
||||
};
|
@ -1,127 +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/. */
|
||||
|
||||
const LINKIFY_TIMEOUT = 0;
|
||||
|
||||
function Linkifier() {
|
||||
this._linkifyTimer = null;
|
||||
this._phoneRegex = /(?:\s|^)[\+]?(\(?\d{1,8}\)?)?([- ]+\(?\d{1,8}\)?)+( ?(x|ext) ?\d{1,3})?(?:\s|$)/g;
|
||||
}
|
||||
|
||||
Linkifier.prototype = {
|
||||
_buildAnchor: function(aDoc, aNumberText) {
|
||||
let anchorNode = aDoc.createElement("a");
|
||||
let cleanedText = "";
|
||||
for (let i = 0; i < aNumberText.length; i++) {
|
||||
let c = aNumberText.charAt(i);
|
||||
if ((c >= "0" && c <= "9") || c == "+") {
|
||||
// assuming there is only the leading '+'.
|
||||
cleanedText += c;
|
||||
}
|
||||
}
|
||||
anchorNode.setAttribute("href", "tel:" + cleanedText);
|
||||
let nodeText = aDoc.createTextNode(aNumberText);
|
||||
anchorNode.appendChild(nodeText);
|
||||
return anchorNode;
|
||||
},
|
||||
|
||||
_linkifyNodeNumbers: function(aNodeToProcess, aDoc) {
|
||||
let parent = aNodeToProcess.parentNode;
|
||||
let nodeText = aNodeToProcess.nodeValue;
|
||||
|
||||
// Replacing the original text node with a sequence of
|
||||
// |text before number|anchor with number|text after number nodes.
|
||||
// Each step a couple of (optional) text node and anchor node are appended.
|
||||
let anchorNode = null;
|
||||
let m = null;
|
||||
let startIndex = 0;
|
||||
let prevNode = null;
|
||||
while ((m = this._phoneRegex.exec(nodeText))) {
|
||||
anchorNode = this._buildAnchor(
|
||||
aDoc,
|
||||
nodeText.substr(m.index, m[0].length)
|
||||
);
|
||||
|
||||
let textExistsBeforeNumber = m.index > startIndex;
|
||||
let nodeToAdd = null;
|
||||
if (textExistsBeforeNumber) {
|
||||
nodeToAdd = aDoc.createTextNode(
|
||||
nodeText.substr(startIndex, m.index - startIndex)
|
||||
);
|
||||
} else {
|
||||
nodeToAdd = anchorNode;
|
||||
}
|
||||
|
||||
if (!prevNode) {
|
||||
// first time, need to replace the whole node with the first new one.
|
||||
parent.replaceChild(nodeToAdd, aNodeToProcess);
|
||||
} else {
|
||||
parent.insertBefore(nodeToAdd, prevNode.nextSibling);
|
||||
} // inserts after.
|
||||
|
||||
if (textExistsBeforeNumber) {
|
||||
// if we added the text node before the anchor, we still need to add the anchor node.
|
||||
parent.insertBefore(anchorNode, nodeToAdd.nextSibling);
|
||||
}
|
||||
|
||||
// next nodes need to be appended to this node.
|
||||
prevNode = anchorNode;
|
||||
startIndex = m.index + m[0].length;
|
||||
}
|
||||
|
||||
// if some text is remaining after the last anchor.
|
||||
if (startIndex > 0 && startIndex < nodeText.length) {
|
||||
let lastNode = aDoc.createTextNode(nodeText.substr(startIndex));
|
||||
parent.insertBefore(lastNode, prevNode.nextSibling);
|
||||
return lastNode;
|
||||
}
|
||||
return anchorNode;
|
||||
},
|
||||
|
||||
linkifyNumbers: function(aDoc) {
|
||||
// Removing any installed timer in case the page has changed and a previous timer is still running.
|
||||
if (this._linkifyTimer) {
|
||||
clearTimeout(this._linkifyTimer);
|
||||
this._linkifyTimer = null;
|
||||
}
|
||||
|
||||
let filterNode = function(node) {
|
||||
if (
|
||||
node.parentNode.tagName != "A" &&
|
||||
node.parentNode.tagName != "SCRIPT" &&
|
||||
node.parentNode.tagName != "NOSCRIPT" &&
|
||||
node.parentNode.tagName != "STYLE" &&
|
||||
node.parentNode.tagName != "APPLET" &&
|
||||
node.parentNode.tagName != "TEXTAREA"
|
||||
) {
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
}
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
};
|
||||
|
||||
let nodeWalker = aDoc.createTreeWalker(
|
||||
aDoc.body,
|
||||
NodeFilter.SHOW_TEXT,
|
||||
filterNode,
|
||||
false
|
||||
);
|
||||
let parseNode = () => {
|
||||
let node = nodeWalker.nextNode();
|
||||
if (!node) {
|
||||
this._linkifyTimer = null;
|
||||
return;
|
||||
}
|
||||
let lastAddedNode = this._linkifyNodeNumbers(node, aDoc);
|
||||
// we assign a different timeout whether the node was processed or not.
|
||||
if (lastAddedNode) {
|
||||
nodeWalker.currentNode = lastAddedNode;
|
||||
this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT);
|
||||
} else {
|
||||
this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT);
|
||||
}
|
||||
};
|
||||
|
||||
this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT);
|
||||
},
|
||||
};
|
@ -1,62 +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/. */
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Snackbars",
|
||||
"resource://gre/modules/Snackbars.jsm"
|
||||
);
|
||||
|
||||
var MasterPassword = {
|
||||
pref: "privacy.masterpassword.enabled",
|
||||
|
||||
get _pk11DB() {
|
||||
delete this._pk11DB;
|
||||
return (this._pk11DB = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
|
||||
Ci.nsIPK11TokenDB
|
||||
));
|
||||
},
|
||||
|
||||
get enabled() {
|
||||
let token = this._pk11DB.getInternalKeyToken();
|
||||
if (token) {
|
||||
return token.hasPassword;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
setPassword: function setPassword(aPassword) {
|
||||
try {
|
||||
let token = this._pk11DB.getInternalKeyToken();
|
||||
if (token.needsUserInit) {
|
||||
token.initPassword(aPassword);
|
||||
} else if (!token.needsLogin()) {
|
||||
token.changePassword("", aPassword);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
dump("MasterPassword.setPassword: " + e);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
removePassword: function removePassword(aOldPassword) {
|
||||
try {
|
||||
let token = this._pk11DB.getInternalKeyToken();
|
||||
if (token.checkPassword(aOldPassword)) {
|
||||
token.changePassword(aOldPassword, "");
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
dump("MasterPassword.removePassword: " + e + "\n");
|
||||
}
|
||||
Snackbars.show(
|
||||
Strings.browser.GetStringFromName("masterPassword.incorrect"),
|
||||
Snackbars.LENGTH_LONG
|
||||
);
|
||||
return false;
|
||||
},
|
||||
};
|
@ -1,89 +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/. */
|
||||
"use strict";
|
||||
|
||||
const MAX_CONTENT_VIEWERS_PREF = "browser.sessionhistory.max_total_viewers";
|
||||
|
||||
var MemoryObserver = {
|
||||
// When we turn off the bfcache by overwriting the old default value, we want
|
||||
// to be able to restore it later on if memory pressure decreases again.
|
||||
_defaultMaxContentViewers: -1,
|
||||
|
||||
observe: function mo_observe(aSubject, aTopic, aData) {
|
||||
if (aTopic == "memory-pressure") {
|
||||
if (aData != "heap-minimize") {
|
||||
this.handleLowMemory();
|
||||
}
|
||||
// The JS engine would normally GC on this notification, but since we
|
||||
// disabled that in favor of this method (bug 669346), we should gc here.
|
||||
// See bug 784040 for when this code was ported from XUL to native Fennec.
|
||||
this.gc();
|
||||
} else if (aTopic == "memory-pressure-stop") {
|
||||
this.handleEnoughMemory();
|
||||
} else if (aTopic == "Memory:Dump") {
|
||||
this.dumpMemoryStats(aData);
|
||||
}
|
||||
},
|
||||
|
||||
handleLowMemory: function() {
|
||||
// do things to reduce memory usage here
|
||||
if (
|
||||
!Services.prefs.getBoolPref("browser.tabs.disableBackgroundZombification")
|
||||
) {
|
||||
let tabs = BrowserApp.tabs;
|
||||
let selected = BrowserApp.selectedTab;
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
if (tabs[i] != selected && !tabs[i].playingAudio) {
|
||||
tabs[i].zombify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change some preferences temporarily for only this session
|
||||
let defaults = Services.prefs.getDefaultBranch(null);
|
||||
|
||||
// Stop using the bfcache
|
||||
if (
|
||||
!Services.prefs.getBoolPref(
|
||||
"browser.sessionhistory.bfcacheIgnoreMemoryPressure"
|
||||
)
|
||||
) {
|
||||
this._defaultMaxContentViewers = defaults.getIntPref(
|
||||
MAX_CONTENT_VIEWERS_PREF
|
||||
);
|
||||
defaults.setIntPref(MAX_CONTENT_VIEWERS_PREF, 0);
|
||||
}
|
||||
},
|
||||
|
||||
handleEnoughMemory: function() {
|
||||
// Re-enable the bfcache
|
||||
let defaults = Services.prefs.getDefaultBranch(null);
|
||||
if (
|
||||
!Services.prefs.getBoolPref(
|
||||
"browser.sessionhistory.bfcacheIgnoreMemoryPressure"
|
||||
)
|
||||
) {
|
||||
defaults.setIntPref(
|
||||
MAX_CONTENT_VIEWERS_PREF,
|
||||
this._defaultMaxContentViewers
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
gc: function() {
|
||||
window.windowUtils.garbageCollect();
|
||||
Cu.forceGC();
|
||||
},
|
||||
|
||||
dumpMemoryStats: function(aLabel) {
|
||||
let memDumper = Cc["@mozilla.org/memory-info-dumper;1"].getService(
|
||||
Ci.nsIMemoryInfoDumper
|
||||
);
|
||||
memDumper.dumpMemoryInfoToTempDir(
|
||||
aLabel,
|
||||
/* anonymize = */ false,
|
||||
/* minimize = */ false
|
||||
);
|
||||
},
|
||||
};
|
@ -1,53 +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/. */
|
||||
"use strict";
|
||||
|
||||
var OfflineApps = {
|
||||
allowSite: function(aDocument) {
|
||||
Services.perms.addFromPrincipal(
|
||||
aDocument.nodePrincipal,
|
||||
"offline-app",
|
||||
Services.perms.ALLOW_ACTION
|
||||
);
|
||||
|
||||
// When a site is enabled while loading, manifest resources will
|
||||
// start fetching immediately. This one time we need to do it
|
||||
// ourselves.
|
||||
this._startFetching(aDocument);
|
||||
},
|
||||
|
||||
disallowSite: function(aDocument) {
|
||||
Services.perms.addFromPrincipal(
|
||||
aDocument.nodePrincipal,
|
||||
"offline-app",
|
||||
Services.perms.DENY_ACTION
|
||||
);
|
||||
},
|
||||
|
||||
_startFetching: function(aDocument) {
|
||||
if (!aDocument.documentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
let manifest = aDocument.documentElement.getAttribute("manifest");
|
||||
if (!manifest) {
|
||||
return;
|
||||
}
|
||||
|
||||
let manifestURI = Services.io.newURI(
|
||||
manifest,
|
||||
aDocument.characterSet,
|
||||
aDocument.documentURIObject
|
||||
);
|
||||
let updateService = Cc[
|
||||
"@mozilla.org/offlinecacheupdate-service;1"
|
||||
].getService(Ci.nsIOfflineCacheUpdateService);
|
||||
updateService.scheduleUpdate(
|
||||
manifestURI,
|
||||
aDocument.documentURIObject,
|
||||
aDocument.nodePrincipal,
|
||||
window
|
||||
);
|
||||
},
|
||||
};
|
@ -1,204 +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/. */
|
||||
"use strict";
|
||||
|
||||
var PermissionsHelper = {
|
||||
_permissonTypes: [
|
||||
"password",
|
||||
"geolocation",
|
||||
"popup",
|
||||
"indexedDB",
|
||||
"offline-app",
|
||||
"desktop-notification",
|
||||
"plugins",
|
||||
"native-intent",
|
||||
],
|
||||
_permissionStrings: {
|
||||
password: {
|
||||
label: "password.logins",
|
||||
allowed: "password.save",
|
||||
denied: "password.dontSave",
|
||||
},
|
||||
geolocation: {
|
||||
label: "geolocation.location",
|
||||
allowed: "geolocation.allow",
|
||||
denied: "geolocation.dontAllow",
|
||||
},
|
||||
popup: {
|
||||
label: "blockPopups.label2",
|
||||
allowed: "popup.show",
|
||||
denied: "popup.dontShow",
|
||||
},
|
||||
indexedDB: {
|
||||
label: "offlineApps.offlineData",
|
||||
allowed: "offlineApps.allow",
|
||||
denied: "offlineApps.dontAllow2",
|
||||
},
|
||||
"offline-app": {
|
||||
label: "offlineApps.offlineData",
|
||||
allowed: "offlineApps.allow",
|
||||
denied: "offlineApps.dontAllow2",
|
||||
},
|
||||
"desktop-notification": {
|
||||
label: "desktopNotification.notifications",
|
||||
allowed: "desktopNotification2.allow",
|
||||
denied: "desktopNotification2.dontAllow",
|
||||
},
|
||||
plugins: {
|
||||
label: "clickToPlayPlugins.plugins",
|
||||
allowed: "clickToPlayPlugins.activate",
|
||||
denied: "clickToPlayPlugins.dontActivate",
|
||||
},
|
||||
"native-intent": {
|
||||
label: "helperapps.openWithList2",
|
||||
allowed: "helperapps.always",
|
||||
denied: "helperapps.never",
|
||||
},
|
||||
},
|
||||
|
||||
onEvent: function onEvent(event, data, callback) {
|
||||
let principal = BrowserApp.selectedBrowser.contentPrincipal;
|
||||
let check = false;
|
||||
|
||||
switch (event) {
|
||||
case "Permissions:Check":
|
||||
check = true;
|
||||
// fall-through
|
||||
|
||||
case "Permissions:Get":
|
||||
let permissions = [];
|
||||
for (let i = 0; i < this._permissonTypes.length; i++) {
|
||||
let type = this._permissonTypes[i];
|
||||
let value = this.getPermission(principal, type);
|
||||
|
||||
// Only add the permission if it was set by the user
|
||||
if (value == Services.perms.UNKNOWN_ACTION) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (check) {
|
||||
GlobalEventDispatcher.sendRequest({
|
||||
type: "Permissions:CheckResult",
|
||||
hasPermissions: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Get the strings that correspond to the permission type
|
||||
let typeStrings = this._permissionStrings[type];
|
||||
let label = Strings.browser.GetStringFromName(typeStrings.label);
|
||||
|
||||
// Get the key to look up the appropriate string entity
|
||||
let valueKey =
|
||||
value == Services.perms.ALLOW_ACTION ? "allowed" : "denied";
|
||||
let valueString = Strings.browser.GetStringFromName(
|
||||
typeStrings[valueKey]
|
||||
);
|
||||
|
||||
permissions.push({
|
||||
type: type,
|
||||
setting: label,
|
||||
value: valueString,
|
||||
});
|
||||
}
|
||||
|
||||
if (check) {
|
||||
GlobalEventDispatcher.sendRequest({
|
||||
type: "Permissions:CheckResult",
|
||||
hasPermissions: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep track of permissions, so we know which ones to clear
|
||||
this._currentPermissions = permissions;
|
||||
|
||||
WindowEventDispatcher.sendRequest({
|
||||
type: "Permissions:Data",
|
||||
permissions: permissions,
|
||||
});
|
||||
break;
|
||||
|
||||
case "Permissions:Clear":
|
||||
// An array of the indices of the permissions we want to clear
|
||||
let permissionsToClear = data.permissions;
|
||||
let privacyContext = BrowserApp.selectedBrowser.docShell.QueryInterface(
|
||||
Ci.nsILoadContext
|
||||
);
|
||||
|
||||
for (let i = 0; i < permissionsToClear.length; i++) {
|
||||
let indexToClear = permissionsToClear[i];
|
||||
let permissionType = this._currentPermissions[indexToClear].type;
|
||||
this.clearPermission(uri, permissionType, privacyContext);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the permission value stored for a specified permission type.
|
||||
*
|
||||
* @param aType
|
||||
* The permission type string stored in permission manager.
|
||||
* e.g. "geolocation", "indexedDB", "popup"
|
||||
*
|
||||
* @return A permission value defined in nsIPermissionManager.
|
||||
*/
|
||||
getPermission: function getPermission(aPrincipal, aType) {
|
||||
let aURI = BrowserApp.selectedBrowser.lastURI;
|
||||
// Password saving isn't a nsIPermissionManager permission type, so handle
|
||||
// it seperately.
|
||||
if (aType == "password") {
|
||||
// By default, login saving is enabled, so if it is disabled, the
|
||||
// user selected the never remember option
|
||||
if (!Services.logins.getLoginSavingEnabled(aURI.displayPrePath)) {
|
||||
return Services.perms.DENY_ACTION;
|
||||
}
|
||||
|
||||
// Check to see if the user ever actually saved a login
|
||||
if (Services.logins.countLogins(aURI.displayPrePath, "", "")) {
|
||||
return Services.perms.ALLOW_ACTION;
|
||||
}
|
||||
|
||||
return Services.perms.UNKNOWN_ACTION;
|
||||
}
|
||||
|
||||
// Geolocation consumers use testExactPermissionForPrincipal
|
||||
if (aType == "geolocation") {
|
||||
return Services.perms.testExactPermissionFromPrincipal(aPrincipal, aType);
|
||||
}
|
||||
|
||||
return Services.perms.testPermissionFromPrincipal(aPrincipal, aType);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears a user-set permission value for the site given a permission type.
|
||||
*
|
||||
* @param aType
|
||||
* The permission type string stored in permission manager.
|
||||
* e.g. "geolocation", "indexedDB", "popup"
|
||||
*/
|
||||
clearPermission: function clearPermission(aPrincipal, aType, aContext) {
|
||||
// Password saving isn't a nsIPermissionManager permission type, so handle
|
||||
// it seperately.
|
||||
if (aType == "password") {
|
||||
// Get rid of exisiting stored logings
|
||||
let logins = Services.logins.findLogins(aURI.displayPrePath, "", "");
|
||||
for (let i = 0; i < logins.length; i++) {
|
||||
Services.logins.removeLogin(logins[i]);
|
||||
}
|
||||
// Re-set login saving to enabled
|
||||
Services.logins.setLoginSavingEnabled(aURI.displayPrePath, true);
|
||||
} else {
|
||||
Services.perms.removeFromPrincipal(aPrincipal, aType);
|
||||
// Clear content prefs set in ContentPermissionPrompt.js
|
||||
Cc["@mozilla.org/content-pref/service;1"]
|
||||
.getService(Ci.nsIContentPrefService2)
|
||||
.removeByDomainAndName(
|
||||
aURI.spec,
|
||||
aType + ".request.remember",
|
||||
aContext
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
@ -1,64 +0,0 @@
|
||||
/* -*- Mode: tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 TOPIC_PRESENTATION_VIEW_READY = "presentation-view-ready";
|
||||
const TOPIC_PRESENTATION_RECEIVER_LAUNCH = "presentation-receiver:launch";
|
||||
const TOPIC_PRESENTATION_RECEIVER_LAUNCH_RESPONSE =
|
||||
"presentation-receiver:launch:response";
|
||||
|
||||
// globals Services
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function log(str) {
|
||||
// dump("-*- PresentationView.js -*-: " + str + "\n");
|
||||
}
|
||||
|
||||
let PresentationView = {
|
||||
_id: null,
|
||||
|
||||
startup: function startup() {
|
||||
// use hash as the ID of this top level window
|
||||
this._id = window.location.hash.substr(1);
|
||||
|
||||
// Listen "presentation-receiver:launch" sent from
|
||||
// PresentationRequestUIGlue.
|
||||
Services.obs.addObserver(this, TOPIC_PRESENTATION_RECEIVER_LAUNCH);
|
||||
|
||||
// Notify PresentationView is ready.
|
||||
Services.obs.notifyObservers(null, TOPIC_PRESENTATION_VIEW_READY, this._id);
|
||||
},
|
||||
|
||||
stop: function stop() {
|
||||
Services.obs.removeObserver(this, TOPIC_PRESENTATION_RECEIVER_LAUNCH);
|
||||
},
|
||||
|
||||
observe: function observe(aSubject, aTopic, aData) {
|
||||
log("Got observe: aTopic=" + aTopic);
|
||||
|
||||
let requestData = JSON.parse(aData);
|
||||
if (this._id != requestData.windowId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let browser = document.getElementById("content");
|
||||
browser.setAttribute("mozpresentation", requestData.url);
|
||||
try {
|
||||
browser.loadURI(requestData.url);
|
||||
Services.obs.notifyObservers(
|
||||
browser,
|
||||
TOPIC_PRESENTATION_RECEIVER_LAUNCH_RESPONSE,
|
||||
JSON.stringify({ result: "success", requestId: requestData.requestId })
|
||||
);
|
||||
} catch (e) {
|
||||
Services.obs.notifyObservers(
|
||||
null,
|
||||
TOPIC_PRESENTATION_RECEIVER_LAUNCH_RESPONSE,
|
||||
JSON.stringify({ result: "error", reason: e.message })
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
<?xml version="1.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/. -->
|
||||
|
||||
<window id="presentation-window"
|
||||
onload="PresentationView.startup();"
|
||||
onunload="PresentationView.stop();"
|
||||
windowtype="navigator:browser"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<browser id="content" type="content" src="about:blank" flex="1"/>
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/PresentationView.js"/>
|
||||
</window>
|
@ -1,92 +0,0 @@
|
||||
// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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";
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Snackbars",
|
||||
"resource://gre/modules/Snackbars.jsm"
|
||||
);
|
||||
|
||||
var PrintHelper = {
|
||||
onEvent: function(event, data, callback) {
|
||||
let browser = BrowserApp.selectedBrowser;
|
||||
|
||||
switch (event) {
|
||||
case "Print:PDF":
|
||||
this.generatePDF(browser).then(
|
||||
data => callback.onSuccess(data),
|
||||
error => callback.onError(error)
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
generatePDF: function(aBrowser) {
|
||||
// Create the final destination file location
|
||||
let fileName = ContentAreaUtils.getDefaultFileName(
|
||||
aBrowser.contentTitle,
|
||||
aBrowser.currentURI,
|
||||
null,
|
||||
null
|
||||
);
|
||||
fileName = fileName.trim() + ".pdf";
|
||||
|
||||
let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
file.append(fileName);
|
||||
file.createUnique(file.NORMAL_FILE_TYPE, parseInt("666", 8));
|
||||
|
||||
let printSettings = Cc[
|
||||
"@mozilla.org/gfx/printsettings-service;1"
|
||||
].getService(Ci.nsIPrintSettingsService).newPrintSettings;
|
||||
printSettings.printSilent = true;
|
||||
printSettings.showPrintProgress = false;
|
||||
printSettings.printBGImages = false;
|
||||
printSettings.printBGColors = false;
|
||||
printSettings.printToFile = true;
|
||||
printSettings.toFileName = file.path;
|
||||
printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
|
||||
|
||||
let webBrowserPrint = aBrowser.contentWindow.getInterface(
|
||||
Ci.nsIWebBrowserPrint
|
||||
);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
webBrowserPrint.print(printSettings, {
|
||||
onStateChange: function(webProgress, request, stateFlags, status) {
|
||||
// We get two STATE_START calls, one for STATE_IS_DOCUMENT and one for STATE_IS_NETWORK
|
||||
if (
|
||||
stateFlags & Ci.nsIWebProgressListener.STATE_START &&
|
||||
stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK
|
||||
) {
|
||||
// Let the user know something is happening. Generating the PDF can take some time.
|
||||
Snackbars.show(
|
||||
Strings.browser.GetStringFromName("alertPrintjobToast"),
|
||||
Snackbars.LENGTH_LONG
|
||||
);
|
||||
}
|
||||
|
||||
// We get two STATE_STOP calls, one for STATE_IS_DOCUMENT and one for STATE_IS_NETWORK
|
||||
if (
|
||||
stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK
|
||||
) {
|
||||
if (Components.isSuccessCode(status)) {
|
||||
// Send the details to Java
|
||||
resolve({ file: file.path, title: fileName });
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
},
|
||||
onProgressChange: function() {},
|
||||
onLocationChange: function() {},
|
||||
onStatusChange: function() {},
|
||||
onSecurityChange: function() {},
|
||||
onContentBlockingEvent: function() {},
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
@ -1,342 +0,0 @@
|
||||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
/* 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";
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Snackbars",
|
||||
"resource://gre/modules/Snackbars.jsm"
|
||||
);
|
||||
|
||||
/* globals MAX_URI_LENGTH, MAX_TITLE_LENGTH */
|
||||
|
||||
var Reader = {
|
||||
// These values should match those defined in BrowserContract.java.
|
||||
STATUS_UNFETCHED: 0,
|
||||
STATUS_FETCH_FAILED_TEMPORARY: 1,
|
||||
STATUS_FETCH_FAILED_PERMANENT: 2,
|
||||
STATUS_FETCH_FAILED_UNSUPPORTED_FORMAT: 3,
|
||||
STATUS_FETCHED_ARTICLE: 4,
|
||||
|
||||
get _hasUsedToolbar() {
|
||||
delete this._hasUsedToolbar;
|
||||
return (this._hasUsedToolbar = Services.prefs.getBoolPref(
|
||||
"reader.has_used_toolbar"
|
||||
));
|
||||
},
|
||||
|
||||
/**
|
||||
* BackPressListener (listeners / ReaderView Ids).
|
||||
*/
|
||||
_backPressListeners: [],
|
||||
_backPressViewIds: [],
|
||||
|
||||
/**
|
||||
* Set a backPressListener for this tabId / ReaderView Id pair.
|
||||
*/
|
||||
_addBackPressListener: function(tabId, viewId, listener) {
|
||||
this._backPressListeners[tabId] = listener;
|
||||
this._backPressViewIds[viewId] = tabId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a backPressListener for this ReaderView Id.
|
||||
*/
|
||||
_removeBackPressListener: function(viewId) {
|
||||
let tabId = this._backPressViewIds[viewId];
|
||||
if (tabId != undefined) {
|
||||
this._backPressListeners[tabId] = null;
|
||||
delete this._backPressViewIds[viewId];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* If the requested tab has a backPress listener, return its results, else false.
|
||||
*/
|
||||
onBackPress: function(tabId) {
|
||||
let listener = this._backPressListeners[tabId];
|
||||
return { handled: listener ? listener() : false };
|
||||
},
|
||||
|
||||
onEvent: function Reader_onEvent(event, data, callback) {
|
||||
switch (event) {
|
||||
case "Reader:RemoveFromCache": {
|
||||
ReaderMode.removeArticleFromCache(data.url).catch(e =>
|
||||
Cu.reportError("Error removing article from cache: " + e)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Reader:AddToCache": {
|
||||
let tab = BrowserApp.getTabForId(data.tabID);
|
||||
if (!tab) {
|
||||
throw new Error(
|
||||
"No tab for tabID = " +
|
||||
data.tabID +
|
||||
" when trying to save reader view article"
|
||||
);
|
||||
}
|
||||
|
||||
// If the article is coming from reader mode, we must have fetched it already.
|
||||
this._getArticleData(tab.browser)
|
||||
.then(article => {
|
||||
ReaderMode.storeArticleInCache(article);
|
||||
})
|
||||
.catch(e => Cu.reportError("Error storing article in cache: " + e));
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function(message) {
|
||||
switch (message.name) {
|
||||
case "Reader:ArticleGet":
|
||||
this._getArticle(message.data.url).then(
|
||||
article => {
|
||||
// Make sure the target browser is still alive before trying to send data back.
|
||||
if (message.target.messageManager) {
|
||||
message.target.messageManager.sendAsyncMessage(
|
||||
"Reader:ArticleData",
|
||||
{ article: article }
|
||||
);
|
||||
}
|
||||
},
|
||||
e => {
|
||||
if (e && e.newURL) {
|
||||
message.target.loadURI(
|
||||
"about:reader?url=" + encodeURIComponent(e.newURL)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
break;
|
||||
|
||||
// On DropdownClosed in ReaderView, we cleanup / clear existing BackPressListener.
|
||||
case "Reader:DropdownClosed": {
|
||||
this._removeBackPressListener(message.data);
|
||||
break;
|
||||
}
|
||||
|
||||
// On DropdownOpened in ReaderView, we add BackPressListener to handle a subsequent BACK request.
|
||||
case "Reader:DropdownOpened": {
|
||||
let tabId = BrowserApp.selectedTab.id;
|
||||
this._addBackPressListener(tabId, message.data, () => {
|
||||
// User hit BACK key while ReaderView has the banner font-dropdown opened.
|
||||
// Close it and return prevent-default.
|
||||
if (message.target.messageManager) {
|
||||
message.target.messageManager.sendAsyncMessage(
|
||||
"Reader:CloseDropdown"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
// We can assume ReaderView banner's font-dropdown doesn't need to be closed.
|
||||
return false;
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "Reader:FaviconRequest": {
|
||||
GlobalEventDispatcher.sendRequestForResult({
|
||||
type: "Reader:FaviconRequest",
|
||||
url: message.data.url,
|
||||
}).then(data => {
|
||||
message.target.messageManager.sendAsyncMessage(
|
||||
"Reader:FaviconReturn",
|
||||
data
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "Reader:SystemUIVisibility":
|
||||
this._showSystemUI(message.data.visible);
|
||||
break;
|
||||
|
||||
case "Reader:ToolbarHidden":
|
||||
if (!this._hasUsedToolbar) {
|
||||
Snackbars.show(
|
||||
Strings.browser.GetStringFromName("readerMode.toolbarTip"),
|
||||
Snackbars.LENGTH_LONG
|
||||
);
|
||||
Services.prefs.setBoolPref("reader.has_used_toolbar", true);
|
||||
this._hasUsedToolbar = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case "Reader:UpdateReaderButton": {
|
||||
let tab = BrowserApp.getTabForBrowser(message.target);
|
||||
tab.browser.isArticle = message.data.isArticle;
|
||||
this.updatePageAction(tab);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
pageAction: {
|
||||
readerModeCallback: function(browser) {
|
||||
let url = browser.currentURI.spec;
|
||||
if (url.startsWith("about:reader")) {
|
||||
UITelemetry.addEvent("action.1", "button", null, "reader_exit");
|
||||
} else {
|
||||
UITelemetry.addEvent("action.1", "button", null, "reader_enter");
|
||||
}
|
||||
browser.messageManager.sendAsyncMessage("Reader:ToggleReaderMode");
|
||||
},
|
||||
},
|
||||
|
||||
updatePageAction: function(tab) {
|
||||
if (!tab.getActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pageAction.id) {
|
||||
PageActions.remove(this.pageAction.id);
|
||||
delete this.pageAction.id;
|
||||
}
|
||||
|
||||
let showPageAction = (icon, title, useTint) => {
|
||||
this.pageAction.id = PageActions.add({
|
||||
icon: icon,
|
||||
title: title,
|
||||
clickCallback: () => this.pageAction.readerModeCallback(browser),
|
||||
important: true,
|
||||
useTint: useTint,
|
||||
});
|
||||
};
|
||||
|
||||
let browser = tab.browser;
|
||||
if (browser.currentURI.spec.startsWith("about:reader")) {
|
||||
showPageAction(
|
||||
"drawable://ic_readermode_on",
|
||||
Strings.reader.GetStringFromName("readerView.close"),
|
||||
false
|
||||
);
|
||||
// Only start a reader session if the viewer is in the foreground. We do
|
||||
// not track background reader viewers.
|
||||
UITelemetry.startSession("reader.1", null);
|
||||
return;
|
||||
}
|
||||
|
||||
// not in ReaderMode, to make sure System UI is visible, not dimmed.
|
||||
this._showSystemUI(true);
|
||||
|
||||
// Only stop a reader session if the foreground viewer is not visible.
|
||||
UITelemetry.stopSession("reader.1", "", null);
|
||||
|
||||
if (browser.isArticle) {
|
||||
showPageAction(
|
||||
"drawable://ic_readermode",
|
||||
Strings.reader.GetStringFromName("readerView.enter"),
|
||||
true
|
||||
);
|
||||
UITelemetry.addEvent("show.1", "button", null, "reader_available");
|
||||
this._sendMmaEvent("reader_available");
|
||||
} else {
|
||||
UITelemetry.addEvent("show.1", "button", null, "reader_unavailable");
|
||||
}
|
||||
},
|
||||
|
||||
_sendMmaEvent: function(event) {
|
||||
WindowEventDispatcher.sendRequest({
|
||||
type: "Mma:" + event,
|
||||
});
|
||||
},
|
||||
|
||||
_showSystemUI: function(visibility) {
|
||||
WindowEventDispatcher.sendRequest({
|
||||
type: "SystemUI:Visibility",
|
||||
visible: visibility,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an article for a given URL. This method will download and parse a document
|
||||
* if it does not find the article in the cache.
|
||||
*
|
||||
* @param url The article URL.
|
||||
* @return {Promise}
|
||||
* @resolves JS object representing the article, or null if no article is found.
|
||||
*/
|
||||
async _getArticle(url) {
|
||||
// First try to find a parsed article in the cache.
|
||||
let article = await ReaderMode.getArticleFromCache(url);
|
||||
if (article) {
|
||||
return article;
|
||||
}
|
||||
|
||||
// Article hasn't been found in the cache, we need to
|
||||
// download the page and parse the article out of it.
|
||||
return ReaderMode.downloadAndParseDocument(url).catch(e => {
|
||||
if (e && e.newURL) {
|
||||
// Pass up the error so we can navigate the browser in question to the new URL:
|
||||
throw e;
|
||||
}
|
||||
Cu.reportError("Error downloading and parsing document: " + e);
|
||||
return null;
|
||||
});
|
||||
},
|
||||
|
||||
_getArticleData: function(browser) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (browser == null) {
|
||||
reject("_getArticleData needs valid browser");
|
||||
}
|
||||
|
||||
let mm = browser.messageManager;
|
||||
let listener = message => {
|
||||
mm.removeMessageListener("Reader:StoredArticleData", listener);
|
||||
resolve(message.data.article);
|
||||
};
|
||||
mm.addMessageListener("Reader:StoredArticleData", listener);
|
||||
mm.sendAsyncMessage("Reader:GetStoredArticleData");
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Migrates old indexedDB reader mode cache to new JSON cache.
|
||||
*/
|
||||
async migrateCache() {
|
||||
let cacheDB = await new Promise((resolve, reject) => {
|
||||
let request = window.indexedDB.open("about:reader", 1);
|
||||
request.onsuccess = event => resolve(event.target.result);
|
||||
request.onerror = event => reject(request.error);
|
||||
|
||||
// If there is no DB to migrate, don't do anything.
|
||||
request.onupgradeneeded = event => resolve(null);
|
||||
});
|
||||
|
||||
if (!cacheDB) {
|
||||
return;
|
||||
}
|
||||
|
||||
let articles = await new Promise((resolve, reject) => {
|
||||
let articles = [];
|
||||
|
||||
let transaction = cacheDB.transaction(cacheDB.objectStoreNames);
|
||||
let store = transaction.objectStore(cacheDB.objectStoreNames[0]);
|
||||
|
||||
let request = store.openCursor();
|
||||
request.onsuccess = event => {
|
||||
let cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
resolve(articles);
|
||||
} else {
|
||||
articles.push(cursor.value);
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
request.onerror = event => reject(request.error);
|
||||
});
|
||||
|
||||
for (let article of articles) {
|
||||
await ReaderMode.storeArticleInCache(article);
|
||||
}
|
||||
|
||||
// Delete the database.
|
||||
window.indexedDB.deleteDatabase("about:reader");
|
||||
},
|
||||
};
|
@ -1,406 +0,0 @@
|
||||
// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
/* globals DebuggerServer */
|
||||
"use strict";
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "require", () => {
|
||||
let { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
|
||||
return require;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "DebuggerServer", () => {
|
||||
let { DebuggerServer } = require("devtools/server/debugger-server");
|
||||
return DebuggerServer;
|
||||
});
|
||||
XPCOMUtils.defineLazyGetter(this, "SocketListener", () => {
|
||||
let { SocketListener } = require("devtools/shared/security/socket");
|
||||
return SocketListener;
|
||||
});
|
||||
|
||||
var RemoteDebugger = {
|
||||
init(aWindow) {
|
||||
this._windowType = "navigator:browser";
|
||||
|
||||
USBRemoteDebugger.init();
|
||||
WiFiRemoteDebugger.init();
|
||||
|
||||
const listener = event => {
|
||||
if (event.target !== aWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newType =
|
||||
event.type === "activate" ? "navigator:browser" : "navigator:geckoview";
|
||||
if (this._windowType === newType) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._windowType = newType;
|
||||
if (this.isAnyEnabled) {
|
||||
this.initServer();
|
||||
}
|
||||
};
|
||||
aWindow.addEventListener("activate", listener, { mozSystemGroup: true });
|
||||
aWindow.addEventListener("deactivate", listener, { mozSystemGroup: true });
|
||||
},
|
||||
|
||||
get isAnyEnabled() {
|
||||
return USBRemoteDebugger.isEnabled || WiFiRemoteDebugger.isEnabled;
|
||||
},
|
||||
|
||||
/**
|
||||
* Prompt the user to accept or decline the incoming connection.
|
||||
*
|
||||
* @param session object
|
||||
* The session object will contain at least the following fields:
|
||||
* {
|
||||
* authentication,
|
||||
* client: {
|
||||
* host,
|
||||
* port
|
||||
* },
|
||||
* server: {
|
||||
* host,
|
||||
* port
|
||||
* }
|
||||
* }
|
||||
* Specific authentication modes may include additional fields. Check
|
||||
* the different |allowConnection| methods in
|
||||
* devtools/shared/security/auth.js.
|
||||
* @return An AuthenticationResult value.
|
||||
* A promise that will be resolved to the above is also allowed.
|
||||
*/
|
||||
allowConnection(session) {
|
||||
if (this._promptingForAllow) {
|
||||
// Don't stack connection prompts if one is already open
|
||||
return DebuggerServer.AuthenticationResult.DENY;
|
||||
}
|
||||
|
||||
if (!session.server.port) {
|
||||
this._promptingForAllow = this._promptForUSB(session);
|
||||
} else {
|
||||
this._promptingForAllow = this._promptForTCP(session);
|
||||
}
|
||||
this._promptingForAllow.then(() => (this._promptingForAllow = null));
|
||||
|
||||
return this._promptingForAllow;
|
||||
},
|
||||
|
||||
_promptForUSB(session) {
|
||||
if (session.authentication !== "PROMPT") {
|
||||
// This dialog is not prepared for any other authentication method at
|
||||
// this time.
|
||||
return DebuggerServer.AuthenticationResult.DENY;
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
let title = Strings.browser.GetStringFromName(
|
||||
"remoteIncomingPromptTitle"
|
||||
);
|
||||
let msg = Strings.browser.GetStringFromName("remoteIncomingPromptUSB");
|
||||
let allow = Strings.browser.GetStringFromName(
|
||||
"remoteIncomingPromptAllow"
|
||||
);
|
||||
let deny = Strings.browser.GetStringFromName("remoteIncomingPromptDeny");
|
||||
|
||||
// Make prompt. Note: button order is in reverse.
|
||||
let prompt = new Prompt({
|
||||
window: null,
|
||||
hint: "remotedebug",
|
||||
title: title,
|
||||
message: msg,
|
||||
buttons: [allow, deny],
|
||||
priority: 1,
|
||||
});
|
||||
|
||||
prompt.show(data => {
|
||||
let result = data.button;
|
||||
if (result === 0) {
|
||||
resolve(DebuggerServer.AuthenticationResult.ALLOW);
|
||||
} else {
|
||||
resolve(DebuggerServer.AuthenticationResult.DENY);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_promptForTCP(session) {
|
||||
if (session.authentication !== "OOB_CERT" || !session.client.cert) {
|
||||
// This dialog is not prepared for any other authentication method at
|
||||
// this time.
|
||||
return DebuggerServer.AuthenticationResult.DENY;
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
let title = Strings.browser.GetStringFromName(
|
||||
"remoteIncomingPromptTitle"
|
||||
);
|
||||
let msg = Strings.browser.formatStringFromName(
|
||||
"remoteIncomingPromptTCP",
|
||||
[session.client.host, session.client.port]
|
||||
);
|
||||
let scan = Strings.browser.GetStringFromName("remoteIncomingPromptScan");
|
||||
let scanAndRemember = Strings.browser.GetStringFromName(
|
||||
"remoteIncomingPromptScanAndRemember"
|
||||
);
|
||||
let deny = Strings.browser.GetStringFromName("remoteIncomingPromptDeny");
|
||||
|
||||
// Make prompt. Note: button order is in reverse.
|
||||
let prompt = new Prompt({
|
||||
window: null,
|
||||
hint: "remotedebug",
|
||||
title: title,
|
||||
message: msg,
|
||||
buttons: [scan, scanAndRemember, deny],
|
||||
priority: 1,
|
||||
});
|
||||
|
||||
prompt.show(data => {
|
||||
let result = data.button;
|
||||
if (result === 0) {
|
||||
resolve(DebuggerServer.AuthenticationResult.ALLOW);
|
||||
} else if (result === 1) {
|
||||
resolve(DebuggerServer.AuthenticationResult.ALLOW_PERSIST);
|
||||
} else {
|
||||
resolve(DebuggerServer.AuthenticationResult.DENY);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* During OOB_CERT authentication, the user must transfer some data through
|
||||
* some out of band mechanism from the client to the server to authenticate
|
||||
* the devices.
|
||||
*
|
||||
* This implementation instructs Fennec to invoke a QR decoder and return the
|
||||
* the data it contains back here.
|
||||
*
|
||||
* @return An object containing:
|
||||
* * sha256: hash(ClientCert)
|
||||
* * k : K(random 128-bit number)
|
||||
* A promise that will be resolved to the above is also allowed.
|
||||
*/
|
||||
receiveOOB() {
|
||||
if (this._receivingOOB) {
|
||||
return this._receivingOOB;
|
||||
}
|
||||
|
||||
this._receivingOOB = WindowEventDispatcher.sendRequestForResult({
|
||||
type: "DevToolsAuth:Scan",
|
||||
}).then(
|
||||
data => {
|
||||
return JSON.parse(data);
|
||||
},
|
||||
() => {
|
||||
let title = Strings.browser.GetStringFromName(
|
||||
"remoteQRScanFailedPromptTitle"
|
||||
);
|
||||
let msg = Strings.browser.GetStringFromName(
|
||||
"remoteQRScanFailedPromptMessage"
|
||||
);
|
||||
let ok = Strings.browser.GetStringFromName(
|
||||
"remoteQRScanFailedPromptOK"
|
||||
);
|
||||
let prompt = new Prompt({
|
||||
window: null,
|
||||
hint: "remotedebug",
|
||||
title: title,
|
||||
message: msg,
|
||||
buttons: [ok],
|
||||
priority: 1,
|
||||
});
|
||||
prompt.show();
|
||||
}
|
||||
);
|
||||
|
||||
this._receivingOOB.then(() => (this._receivingOOB = null));
|
||||
|
||||
return this._receivingOOB;
|
||||
},
|
||||
|
||||
initServer: function() {
|
||||
DebuggerServer.init();
|
||||
|
||||
// Add browser and Fennec specific actors
|
||||
DebuggerServer.registerAllActors();
|
||||
const {
|
||||
createRootActor,
|
||||
} = require("resource://gre/modules/dbg-browser-actors.js");
|
||||
DebuggerServer.setRootActor(createRootActor);
|
||||
|
||||
// Allow debugging of chrome for any process
|
||||
DebuggerServer.allowChromeProcess = true;
|
||||
DebuggerServer.chromeWindowType = this._windowType;
|
||||
// Force the Server to stay alive even if there are no connections at the moment.
|
||||
DebuggerServer.keepAlive = true;
|
||||
},
|
||||
};
|
||||
|
||||
RemoteDebugger.allowConnection = RemoteDebugger.allowConnection.bind(
|
||||
RemoteDebugger
|
||||
);
|
||||
RemoteDebugger.receiveOOB = RemoteDebugger.receiveOOB.bind(RemoteDebugger);
|
||||
|
||||
var USBRemoteDebugger = {
|
||||
init() {
|
||||
Services.prefs.addObserver("devtools.", this);
|
||||
|
||||
if (this.isEnabled) {
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (topic != "nsPref:changed") {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data) {
|
||||
case "devtools.remote.usb.enabled":
|
||||
Services.prefs.setBoolPref(
|
||||
"devtools.debugger.remote-enabled",
|
||||
RemoteDebugger.isAnyEnabled
|
||||
);
|
||||
if (this.isEnabled) {
|
||||
this.start();
|
||||
} else {
|
||||
this.stop();
|
||||
}
|
||||
break;
|
||||
|
||||
case "devtools.debugger.remote-port":
|
||||
case "devtools.debugger.unix-domain-socket":
|
||||
if (this.isEnabled) {
|
||||
this.stop();
|
||||
this.start();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
get isEnabled() {
|
||||
return Services.prefs.getBoolPref("devtools.remote.usb.enabled");
|
||||
},
|
||||
|
||||
start: function() {
|
||||
if (this._listener) {
|
||||
return;
|
||||
}
|
||||
|
||||
RemoteDebugger.initServer();
|
||||
|
||||
const portOrPath =
|
||||
Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") ||
|
||||
Services.prefs.getIntPref("devtools.debugger.remote-port");
|
||||
|
||||
try {
|
||||
dump("Starting USB debugger on " + portOrPath);
|
||||
const AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
|
||||
const authenticator = new AuthenticatorType.Server();
|
||||
authenticator.allowConnection = RemoteDebugger.allowConnection;
|
||||
const socketOptions = { authenticator, portOrPath };
|
||||
this._listener = new SocketListener(DebuggerServer, socketOptions);
|
||||
this._listener.open();
|
||||
} catch (e) {
|
||||
dump("Unable to start USB debugger server: " + e);
|
||||
}
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
if (!this._listener) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._listener.close();
|
||||
this._listener = null;
|
||||
} catch (e) {
|
||||
dump("Unable to stop USB debugger server: " + e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var WiFiRemoteDebugger = {
|
||||
init() {
|
||||
Services.prefs.addObserver("devtools.", this);
|
||||
|
||||
if (this.isEnabled) {
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (topic != "nsPref:changed") {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data) {
|
||||
case "devtools.remote.wifi.enabled":
|
||||
Services.prefs.setBoolPref(
|
||||
"devtools.debugger.remote-enabled",
|
||||
RemoteDebugger.isAnyEnabled
|
||||
);
|
||||
// Allow remote debugging on non-local interfaces when WiFi debug is
|
||||
// enabled
|
||||
// TODO: Bug 1034411: Lock down to WiFi interface only
|
||||
Services.prefs.setBoolPref(
|
||||
"devtools.debugger.force-local",
|
||||
!this.isEnabled
|
||||
);
|
||||
if (this.isEnabled) {
|
||||
this.start();
|
||||
} else {
|
||||
this.stop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
get isEnabled() {
|
||||
return Services.prefs.getBoolPref("devtools.remote.wifi.enabled");
|
||||
},
|
||||
|
||||
start: function() {
|
||||
if (this._listener) {
|
||||
return;
|
||||
}
|
||||
|
||||
RemoteDebugger.initServer();
|
||||
|
||||
try {
|
||||
dump("Starting WiFi debugger");
|
||||
const AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT");
|
||||
const authenticator = new AuthenticatorType.Server();
|
||||
authenticator.allowConnection = RemoteDebugger.allowConnection;
|
||||
authenticator.receiveOOB = RemoteDebugger.receiveOOB;
|
||||
const socketOptions = {
|
||||
authenticator,
|
||||
discoverable: true,
|
||||
encryption: true,
|
||||
portOrPath: -1,
|
||||
};
|
||||
this._listener = new SocketListener(DebuggerServer, socketOptions);
|
||||
this._listener.open();
|
||||
let port = this._listener.port;
|
||||
dump("Started WiFi debugger on " + port);
|
||||
} catch (e) {
|
||||
dump("Unable to start WiFi debugger server: " + e);
|
||||
}
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
if (!this._listener) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._listener.close();
|
||||
this._listener = null;
|
||||
} catch (e) {
|
||||
dump("Unable to stop WiFi debugger server: " + e);
|
||||
}
|
||||
},
|
||||
};
|
@ -1,147 +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/. */
|
||||
|
||||
const {EventDispatcher} = ChromeUtils.import("resource://gre/modules/Messaging.jsm");
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function init() {
|
||||
// Include the build date and a warning about Telemetry
|
||||
// if this is an "a#" (nightly or aurora) build
|
||||
#expand const version = "__MOZ_APP_VERSION_DISPLAY__";
|
||||
if (/a\d+$/.test(version)) {
|
||||
let buildID = Services.appinfo.appBuildID;
|
||||
let buildDate = buildID.slice(0, 4) + "-" + buildID.slice(4, 6) + "-" + buildID.slice(6, 8);
|
||||
let br = document.createElement("br");
|
||||
let versionPara = document.getElementById("version");
|
||||
versionPara.appendChild(br);
|
||||
let date = document.createTextNode("(" + buildDate + ")");
|
||||
versionPara.appendChild(date);
|
||||
document.getElementById("telemetry").hidden = false;
|
||||
}
|
||||
|
||||
// Include the Distribution information if available
|
||||
try {
|
||||
let distroId = Services.prefs.getCharPref("distribution.id");
|
||||
if (distroId) {
|
||||
let distroVersion = Services.prefs.getCharPref("distribution.version");
|
||||
let distroIdField = document.getElementById("distributionID");
|
||||
distroIdField.textContent = distroId + " - " + distroVersion;
|
||||
distroIdField.hidden = false;
|
||||
|
||||
let distroAbout = Services.prefs.getStringPref("distribution.about");
|
||||
let distroField = document.getElementById("distributionAbout");
|
||||
distroField.textContent = distroAbout;
|
||||
distroField.hidden = false;
|
||||
}
|
||||
} catch (e) {
|
||||
// Pref is unset
|
||||
}
|
||||
|
||||
// get URLs from prefs
|
||||
try {
|
||||
let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
|
||||
|
||||
let links = [
|
||||
{id: "releaseNotesURL", pref: "app.releaseNotesURL"},
|
||||
{id: "supportURL", pref: "app.supportURL"},
|
||||
{id: "faqURL", pref: "app.faqURL"},
|
||||
{id: "privacyURL", pref: "app.privacyURL"},
|
||||
{id: "creditsURL", pref: "app.creditsURL"},
|
||||
];
|
||||
|
||||
links.forEach(function(link) {
|
||||
let url = formatter.formatURLPref(link.pref);
|
||||
let element = document.getElementById(link.id);
|
||||
if (element) {
|
||||
element.setAttribute("href", url);
|
||||
}
|
||||
});
|
||||
} catch (ex) {}
|
||||
|
||||
#ifdef MOZ_UPDATER
|
||||
function expectUpdateResult() {
|
||||
EventDispatcher.instance.registerListener(function listener(event, data, callback) {
|
||||
EventDispatcher.instance.unregisterListener(listener, event);
|
||||
showUpdateMessage(data.result);
|
||||
}, "Update:CheckResult");
|
||||
}
|
||||
|
||||
function checkForUpdates() {
|
||||
showCheckingMessage();
|
||||
expectUpdateResult();
|
||||
|
||||
EventDispatcher.instance.sendRequest({ type: "Update:Check" });
|
||||
}
|
||||
|
||||
function downloadUpdate() {
|
||||
expectUpdateResult();
|
||||
|
||||
EventDispatcher.instance.sendRequest({ type: "Update:Download" });
|
||||
}
|
||||
|
||||
function installUpdate() {
|
||||
showCheckAction();
|
||||
|
||||
EventDispatcher.instance.sendRequest({ type: "Update:Install" });
|
||||
}
|
||||
|
||||
let updateLink = document.getElementById("updateLink");
|
||||
let checkingSpan = document.getElementById("update-message-checking");
|
||||
let noneSpan = document.getElementById("update-message-none");
|
||||
let foundSpan = document.getElementById("update-message-found");
|
||||
let downloadingSpan = document.getElementById("update-message-downloading");
|
||||
let downloadedSpan = document.getElementById("update-message-downloaded");
|
||||
|
||||
updateLink.onclick = checkForUpdates;
|
||||
foundSpan.onclick = downloadUpdate;
|
||||
downloadedSpan.onclick = installUpdate;
|
||||
|
||||
function showCheckAction() {
|
||||
checkingSpan.style.display = "none";
|
||||
noneSpan.style.display = "none";
|
||||
foundSpan.style.display = "none";
|
||||
downloadingSpan.style.display = "none";
|
||||
downloadedSpan.style.display = "none";
|
||||
updateLink.style.display = "block";
|
||||
}
|
||||
|
||||
function showCheckingMessage() {
|
||||
updateLink.style.display = "none";
|
||||
noneSpan.style.display = "none";
|
||||
foundSpan.style.display = "none";
|
||||
downloadingSpan.style.display = "none";
|
||||
downloadedSpan.style.display = "none";
|
||||
checkingSpan.style.display = "block";
|
||||
}
|
||||
|
||||
function showUpdateMessage(aResult) {
|
||||
updateLink.style.display = "none";
|
||||
checkingSpan.style.display = "none";
|
||||
noneSpan.style.display = "none";
|
||||
foundSpan.style.display = "none";
|
||||
downloadingSpan.style.display = "none";
|
||||
downloadedSpan.style.display = "none";
|
||||
|
||||
// the aResult values come from mobile/android/base/UpdateServiceHelper.java
|
||||
switch (aResult) {
|
||||
case "NOT_AVAILABLE":
|
||||
noneSpan.style.display = "block";
|
||||
setTimeout(showCheckAction, 2000);
|
||||
break;
|
||||
case "AVAILABLE":
|
||||
foundSpan.style.display = "block";
|
||||
break;
|
||||
case "DOWNLOADING":
|
||||
downloadingSpan.style.display = "block";
|
||||
expectUpdateResult();
|
||||
break;
|
||||
case "DOWNLOADED":
|
||||
downloadedSpan.style.display = "block";
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", init);
|
@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % fennecDTD SYSTEM "chrome://browser/locale/about.dtd">
|
||||
%fennecDTD;
|
||||
]>
|
||||
|
||||
<!-- 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 xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta name="viewport" content="width=480; initial-scale=.6667; user-scalable=no"/>
|
||||
<title>&aboutPage.title;</title>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutPage.css" type="text/css"/>
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<div id="header" dir="ltr">
|
||||
<div id="wordmark"></div>
|
||||
#expand <p id="version">__MOZ_APP_VERSION_DISPLAY__</p>
|
||||
</div>
|
||||
|
||||
<div id="banner">
|
||||
<div id="logo"/>
|
||||
#ifdef MOZ_UPDATER
|
||||
<div id="updateBox">
|
||||
<a id="updateLink" href="">&aboutPage.checkForUpdates.link;</a>
|
||||
<span id="update-message-checking">&aboutPage.checkForUpdates.checking;</span>
|
||||
<span id="update-message-none">&aboutPage.checkForUpdates.none;</span>
|
||||
<span id="update-message-found">&aboutPage.checkForUpdates.available2;</span>
|
||||
<span id="update-message-downloading">&aboutPage.checkForUpdates.downloading;</span>
|
||||
<span id="update-message-downloaded">&aboutPage.checkForUpdates.downloaded2;</span>
|
||||
</div>
|
||||
#endif
|
||||
|
||||
<div id="messages">
|
||||
<p id="distributionAbout" hidden="true"/>
|
||||
<p id="distributionID" hidden="true"/>
|
||||
<p id="telemetry" hidden="true">
|
||||
&aboutPage.warningVersion;
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ul id="aboutLinks">
|
||||
<div class="top-border"></div>
|
||||
<li><a id="faqURL">&aboutPage.faq.label;</a></li>
|
||||
<li><a id="supportURL">&aboutPage.support.label;</a></li>
|
||||
<li><a id="privacyURL">&aboutPage.privacyPolicy.label;</a></li>
|
||||
<li><a href="about:rights">&aboutPage.rights.label;</a></li>
|
||||
#ifndef NIGHTLY_BUILD
|
||||
#ifndef FENNEC_NIGHTLY
|
||||
<li><a id="releaseNotesURL">&aboutPage.relNotes.label;</a></li>
|
||||
#endif
|
||||
#endif
|
||||
<li><a id="creditsURL">&aboutPage.credits.label;</a></li>
|
||||
<li><a href="about:license">&aboutPage.license.label;</a></li>
|
||||
<div class="bottom-border"></div>
|
||||
</ul>
|
||||
|
||||
#ifdef RELEASE_OR_BETA
|
||||
#ifndef FENNEC_NIGHTLY
|
||||
<div id="aboutDetails">
|
||||
<p>&aboutPage.logoTrademark;</p>
|
||||
</div>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/about.js" />
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,377 +0,0 @@
|
||||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Wrap a remote fxa-content-server.
|
||||
*
|
||||
* An about:accounts tab loads and displays an fxa-content-server page,
|
||||
* depending on the current Android Account status and an optional 'action'
|
||||
* parameter.
|
||||
*
|
||||
* We show a spinner while the remote iframe is loading. We expect the
|
||||
* WebChannel message listening to the fxa-content-server to send this tab's
|
||||
* <browser>'s messageManager a LOADED message when the remote iframe provides
|
||||
* the WebChannel LOADED message. See the messageManager registration and the
|
||||
* |loadedDeferred| promise. This loosely couples the WebChannel implementation
|
||||
* and about:accounts! (We need this coupling in order to distinguish
|
||||
* WebChannel LOADED messages produced by multiple about:accounts tabs.)
|
||||
*
|
||||
* We capture error conditions by accessing the inner nsIWebNavigation of the
|
||||
* iframe directly.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Accounts } = ChromeUtils.import("resource://gre/modules/Accounts.jsm");
|
||||
const { PromiseUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/PromiseUtils.jsm"
|
||||
);
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
const ACTION_URL_PARAM = "action";
|
||||
|
||||
const COMMAND_LOADED = "fxaccounts:loaded";
|
||||
|
||||
const log = ChromeUtils.import(
|
||||
"resource://gre/modules/AndroidLog.jsm",
|
||||
{}
|
||||
).AndroidLog.bind("FxAccounts");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"ParentalControls",
|
||||
"@mozilla.org/parental-controls-service;1",
|
||||
"nsIParentalControlsService"
|
||||
);
|
||||
|
||||
// Shows the toplevel element with |id| to be shown - all other top-level
|
||||
// elements are hidden.
|
||||
// If |id| is 'spinner', then 'remote' is also shown, with opacity 0.
|
||||
function show(id) {
|
||||
let allTop = document.querySelectorAll(".toplevel");
|
||||
for (let elt of allTop) {
|
||||
if (elt.getAttribute("id") == id) {
|
||||
elt.style.display = "block";
|
||||
} else {
|
||||
elt.style.display = "none";
|
||||
}
|
||||
}
|
||||
if (id == "spinner") {
|
||||
document.getElementById("remote").style.display = "block";
|
||||
document.getElementById("remote").style.opacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Each time we try to load the remote <iframe>, loadedDeferred is replaced. It
|
||||
// is resolved by a LOADED message, and rejected by a failure to load.
|
||||
var loadedDeferred = null;
|
||||
|
||||
// We have a new load starting. Replace the existing promise with a new one,
|
||||
// and queue up the transition to remote content.
|
||||
function deferTransitionToRemoteAfterLoaded() {
|
||||
log.d("Waiting for LOADED message.");
|
||||
|
||||
loadedDeferred = PromiseUtils.defer();
|
||||
loadedDeferred.promise
|
||||
.then(() => {
|
||||
log.d("Got LOADED message!");
|
||||
document.getElementById("remote").style.opacity = 0;
|
||||
show("remote");
|
||||
document.getElementById("remote").style.opacity = 1;
|
||||
})
|
||||
.catch(e => {
|
||||
log.w("Did not get LOADED message: " + e.toString());
|
||||
});
|
||||
}
|
||||
|
||||
function handleLoadedMessage(message) {
|
||||
loadedDeferred.resolve();
|
||||
}
|
||||
|
||||
var wrapper = {
|
||||
iframe: null,
|
||||
|
||||
url: null,
|
||||
|
||||
init: function(url) {
|
||||
this.url = url;
|
||||
deferTransitionToRemoteAfterLoaded();
|
||||
|
||||
let iframe = document.getElementById("remote");
|
||||
this.iframe = iframe;
|
||||
let docShell = this.iframe.frameLoader.docShell;
|
||||
docShell.QueryInterface(Ci.nsIWebProgress);
|
||||
docShell.addProgressListener(
|
||||
this.iframeListener,
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT |
|
||||
Ci.nsIWebProgress.NOTIFY_LOCATION
|
||||
);
|
||||
|
||||
// Set the iframe's location with loadURI/LOAD_FLAGS_BYPASS_HISTORY to
|
||||
// avoid having a new history entry being added.
|
||||
let webNav = iframe.frameLoader.docShell.QueryInterface(
|
||||
Ci.nsIWebNavigation
|
||||
);
|
||||
let loadURIOptions = {
|
||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY,
|
||||
};
|
||||
webNav.loadURI(url, loadURIOptions);
|
||||
},
|
||||
|
||||
retry: function() {
|
||||
deferTransitionToRemoteAfterLoaded();
|
||||
|
||||
let webNav = this.iframe.frameLoader.docShell.QueryInterface(
|
||||
Ci.nsIWebNavigation
|
||||
);
|
||||
let loadURIOptions = {
|
||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY,
|
||||
};
|
||||
webNav.loadURI(this.url, loadURIOptions);
|
||||
},
|
||||
|
||||
iframeListener: {
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]),
|
||||
|
||||
onStateChange: function(aWebProgress, aRequest, aState, aStatus) {
|
||||
let failure = false;
|
||||
|
||||
// Captive portals sometimes redirect users
|
||||
if (aState & Ci.nsIWebProgressListener.STATE_REDIRECTING) {
|
||||
failure = true;
|
||||
} else if (aState & Ci.nsIWebProgressListener.STATE_STOP) {
|
||||
if (aRequest instanceof Ci.nsIHttpChannel) {
|
||||
try {
|
||||
failure = aRequest.responseStatus != 200;
|
||||
} catch (e) {
|
||||
failure = aStatus != Cr.NS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calling cancel() will raise some OnStateChange notifications by itself,
|
||||
// so avoid doing that more than once
|
||||
if (failure && aStatus != Cr.NS_BINDING_ABORTED) {
|
||||
aRequest.cancel(Cr.NS_BINDING_ABORTED);
|
||||
// Since after a promise is fulfilled, subsequent fulfillments are
|
||||
// treated as no-ops, we don't care that we might see multiple failures
|
||||
// due to multiple listener callbacks. (It's not easy to extract this
|
||||
// from the Promises spec, but it is widely quoted. Start with
|
||||
// http://stackoverflow.com/a/18218542.)
|
||||
loadedDeferred.reject(new Error("Failed in onStateChange!"));
|
||||
show("networkError");
|
||||
}
|
||||
},
|
||||
|
||||
onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
|
||||
if (
|
||||
aRequest &&
|
||||
aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE
|
||||
) {
|
||||
aRequest.cancel(Cr.NS_BINDING_ABORTED);
|
||||
// As above, we're not concerned by multiple listener callbacks.
|
||||
loadedDeferred.reject(new Error("Failed in onLocationChange!"));
|
||||
show("networkError");
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function retry() {
|
||||
log.i("Retrying.");
|
||||
show("spinner");
|
||||
wrapper.retry();
|
||||
}
|
||||
|
||||
function openPrefs() {
|
||||
log.i("Opening Sync preferences.");
|
||||
// If an Android Account exists, this will open the Status Activity.
|
||||
// Otherwise, it will begin the Get Started flow. This should only be shown
|
||||
// when an Account actually exists.
|
||||
Accounts.launchSetup();
|
||||
}
|
||||
|
||||
function getURLForAction(action, urlParams) {
|
||||
let url = Services.urlFormatter.formatURLPref(
|
||||
"identity.fxaccounts.remote.webchannel.uri"
|
||||
);
|
||||
url = url + (url.endsWith("/") ? "" : "/") + action;
|
||||
const CONTEXT = "fx_fennec_v1";
|
||||
// The only service managed by Fennec, to date, is Firefox Sync.
|
||||
const SERVICE = "sync";
|
||||
urlParams = urlParams || new URLSearchParams("");
|
||||
urlParams.set("service", SERVICE);
|
||||
urlParams.set("context", CONTEXT);
|
||||
// Ideally we'd just merge urlParams with new URL(url).searchParams, but our
|
||||
// URLSearchParams implementation doesn't support iteration (bug 1085284).
|
||||
let urlParamStr = urlParams.toString();
|
||||
if (urlParamStr) {
|
||||
url += (url.includes("?") ? "&" : "?") + urlParamStr;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
function updateDisplayedEmail(user) {
|
||||
let emailDiv = document.getElementById("email");
|
||||
if (emailDiv && user) {
|
||||
emailDiv.textContent = user.email;
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
// Test for restrictions before getFirefoxAccount(), since that will fail if
|
||||
// we are restricted.
|
||||
if (!ParentalControls.isAllowed(ParentalControls.MODIFY_ACCOUNTS)) {
|
||||
// It's better to log and show an error message than to invite user
|
||||
// confusion by removing about:accounts entirely. That is, if the user is
|
||||
// restricted, this way they'll discover as much and may be able to get
|
||||
// out of their restricted profile. If we remove about:accounts entirely,
|
||||
// it will look like Fennec is buggy, and the user will be very confused.
|
||||
log.e(
|
||||
"This profile cannot connect to Firefox Accounts: showing restricted error."
|
||||
);
|
||||
show("restrictedError");
|
||||
return;
|
||||
}
|
||||
|
||||
Accounts.getFirefoxAccount()
|
||||
.then(user => {
|
||||
// It's possible for the window to start closing before getting the user
|
||||
// completes. Tests in particular can cause this.
|
||||
if (window.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateDisplayedEmail(user);
|
||||
|
||||
// Ideally we'd use new URL(document.URL).searchParams, but for about: URIs,
|
||||
// searchParams is empty.
|
||||
let urlParams = new URLSearchParams(document.URL.split("?")[1] || "");
|
||||
let action = urlParams.get(ACTION_URL_PARAM);
|
||||
urlParams.delete(ACTION_URL_PARAM);
|
||||
|
||||
switch (action) {
|
||||
case "signup":
|
||||
if (user) {
|
||||
// Asking to sign-up when already signed in just shows prefs.
|
||||
show("prefs");
|
||||
} else {
|
||||
show("spinner");
|
||||
wrapper.init(getURLForAction("signup", urlParams));
|
||||
}
|
||||
break;
|
||||
case "signin":
|
||||
if (user) {
|
||||
// Asking to sign-in when already signed in just shows prefs.
|
||||
show("prefs");
|
||||
} else {
|
||||
show("spinner");
|
||||
wrapper.init(getURLForAction("signin", urlParams));
|
||||
}
|
||||
break;
|
||||
case "force_auth":
|
||||
if (user) {
|
||||
show("spinner");
|
||||
urlParams.set("email", user.email); // In future, pin using the UID.
|
||||
wrapper.init(getURLForAction("force_auth", urlParams));
|
||||
} else {
|
||||
show("spinner");
|
||||
wrapper.init(getURLForAction("signup", urlParams));
|
||||
}
|
||||
break;
|
||||
case "manage":
|
||||
if (user) {
|
||||
show("spinner");
|
||||
urlParams.set("email", user.email); // In future, pin using the UID.
|
||||
wrapper.init(getURLForAction("settings", urlParams));
|
||||
} else {
|
||||
show("spinner");
|
||||
wrapper.init(getURLForAction("signup", urlParams));
|
||||
}
|
||||
break;
|
||||
case "avatar":
|
||||
if (user) {
|
||||
show("spinner");
|
||||
urlParams.set("email", user.email); // In future, pin using the UID.
|
||||
wrapper.init(getURLForAction("settings/avatar/change", urlParams));
|
||||
} else {
|
||||
show("spinner");
|
||||
wrapper.init(getURLForAction("signup", urlParams));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Unrecognized or no action specified.
|
||||
if (action) {
|
||||
log.w("Ignoring unrecognized action: " + action);
|
||||
}
|
||||
if (user) {
|
||||
show("prefs");
|
||||
} else {
|
||||
show("spinner");
|
||||
wrapper.init(getURLForAction("signup", urlParams));
|
||||
}
|
||||
break;
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
log.e("Failed to get the signed in user: " + e.toString());
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener(
|
||||
"DOMContentLoaded",
|
||||
function() {
|
||||
init();
|
||||
var buttonRetry = document.getElementById("buttonRetry");
|
||||
buttonRetry.addEventListener("click", retry);
|
||||
|
||||
var buttonOpenPrefs = document.getElementById("buttonOpenPrefs");
|
||||
buttonOpenPrefs.addEventListener("click", openPrefs);
|
||||
},
|
||||
{ capture: true, once: true }
|
||||
);
|
||||
|
||||
// This window is contained in a XUL <browser> element. Return the
|
||||
// messageManager of that <browser> element, or null.
|
||||
function getBrowserMessageManager() {
|
||||
let browser = window.docShell.rootTreeItem.domWindow.BrowserApp.getBrowserForDocument(
|
||||
document
|
||||
);
|
||||
if (browser) {
|
||||
return browser.messageManager;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add a single listener for 'loaded' messages from the iframe in this
|
||||
// <browser>. These 'loaded' messages are ferried from the WebChannel to just
|
||||
// this <browser>.
|
||||
var mm = getBrowserMessageManager();
|
||||
if (mm) {
|
||||
mm.addMessageListener(COMMAND_LOADED, handleLoadedMessage);
|
||||
} else {
|
||||
log.e("No messageManager, not listening for LOADED message!");
|
||||
}
|
||||
|
||||
window.addEventListener("unload", function(event) {
|
||||
try {
|
||||
let mm = getBrowserMessageManager();
|
||||
if (mm) {
|
||||
mm.removeMessageListener(COMMAND_LOADED, handleLoadedMessage);
|
||||
}
|
||||
} catch (e) {
|
||||
// This could fail if the page is being torn down, the tab is being
|
||||
// destroyed, etc.
|
||||
log.w("Not removing listener for LOADED message: " + e.toString());
|
||||
}
|
||||
});
|
@ -1,83 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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/. -->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
|
||||
%globalDTD;
|
||||
<!ENTITY % aboutDTD SYSTEM "chrome://browser/locale/aboutAccounts.dtd">
|
||||
%aboutDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" dir="&locale.dir;">
|
||||
<head>
|
||||
<title>Firefox Sync</title>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
||||
<link rel="stylesheet" href="chrome://browser/skin/spinner.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutAccounts.css" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="spinner" class="toplevel">
|
||||
<div class="container flex-column">
|
||||
<!-- Empty text-container for spacing. -->
|
||||
<div class="text-container flex-column" />
|
||||
|
||||
<div class="mui-refresh-main">
|
||||
<div class="mui-refresh-wrapper">
|
||||
<div class="mui-spinner-wrapper">
|
||||
<div class="mui-spinner-main">
|
||||
<div class="mui-spinner-left">
|
||||
<div class="mui-half-circle-left" />
|
||||
</div>
|
||||
<div class="mui-spinner-right">
|
||||
<div class="mui-half-circle-right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<iframe mozframetype="content" id="remote" class="toplevel" />
|
||||
|
||||
<div id="prefs" class="toplevel">
|
||||
<div class="container flex-column">
|
||||
<div class="text-container flex-column">
|
||||
<div class="text">&aboutAccounts.connected.title;</div>
|
||||
<div class="hint">&aboutAccounts.connected.description;</div>
|
||||
<div id="email" class="hint"></div>
|
||||
</div>
|
||||
<a id="buttonOpenPrefs" tabindex="0" href="#">&aboutAccounts.syncPreferences.label;</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="networkError" class="toplevel">
|
||||
<div class="container flex-column">
|
||||
<div class="text-container flex-column">
|
||||
<div class="text">&aboutAccounts.noConnection.title;</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button id="buttonRetry" class="button" tabindex="1">&aboutAccounts.retry.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="restrictedError" class="toplevel">
|
||||
<div class="container flex-column">
|
||||
<div class="text-container flex-column">
|
||||
<div class="text">&aboutAccounts.restrictedError.title;</div>
|
||||
<div class="hint">&aboutAccounts.restrictedError.description;</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/aboutAccounts.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,897 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* globals gChromeWin */
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { AddonManager } = ChromeUtils.import(
|
||||
"resource://gre/modules/AddonManager.jsm"
|
||||
);
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
const { EventDispatcher } = ChromeUtils.import(
|
||||
"resource://gre/modules/Messaging.jsm"
|
||||
);
|
||||
|
||||
const AMO_ICON = "chrome://browser/skin/images/amo-logo.png";
|
||||
const UPDATE_INDICATOR = "chrome://browser/skin/images/extension-update.svg";
|
||||
|
||||
var gStringBundle = Services.strings.createBundle(
|
||||
"chrome://browser/locale/aboutAddons.properties"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(window, "gChromeWin", function() {
|
||||
return window.docShell.rootTreeItem.domWindow;
|
||||
});
|
||||
ChromeUtils.defineModuleGetter(
|
||||
window,
|
||||
"Preferences",
|
||||
"resource://gre/modules/Preferences.jsm"
|
||||
);
|
||||
|
||||
var ContextMenus = {
|
||||
target: null,
|
||||
|
||||
init: function() {
|
||||
document.addEventListener("contextmenu", this);
|
||||
|
||||
document
|
||||
.getElementById("contextmenu-enable")
|
||||
.addEventListener("click", ContextMenus.enable.bind(this));
|
||||
document
|
||||
.getElementById("contextmenu-disable")
|
||||
.addEventListener("click", ContextMenus.disable.bind(this));
|
||||
document
|
||||
.getElementById("contextmenu-uninstall")
|
||||
.addEventListener("click", ContextMenus.uninstall.bind(this));
|
||||
|
||||
// XXX - Hack to fix bug 985867 for now
|
||||
document.addEventListener("touchstart", function() {});
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
// store the target of context menu events so that we know which app to act on
|
||||
this.target = event.target;
|
||||
while (!this.target.hasAttribute("contextmenu")) {
|
||||
this.target = this.target.parentNode;
|
||||
}
|
||||
|
||||
if (!this.target) {
|
||||
document
|
||||
.getElementById("contextmenu-enable")
|
||||
.setAttribute("hidden", "true");
|
||||
document
|
||||
.getElementById("contextmenu-disable")
|
||||
.setAttribute("hidden", "true");
|
||||
document
|
||||
.getElementById("contextmenu-uninstall")
|
||||
.setAttribute("hidden", "true");
|
||||
return;
|
||||
}
|
||||
|
||||
let addon = this.target.addon;
|
||||
if (addon.scope == AddonManager.SCOPE_APPLICATION) {
|
||||
document
|
||||
.getElementById("contextmenu-uninstall")
|
||||
.setAttribute("hidden", "true");
|
||||
} else {
|
||||
document
|
||||
.getElementById("contextmenu-uninstall")
|
||||
.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
// Hide the enable/disable context menu items if the add-on was disabled by
|
||||
// Firefox (e.g. unsigned or blocklisted add-on).
|
||||
if (addon.appDisabled) {
|
||||
document
|
||||
.getElementById("contextmenu-enable")
|
||||
.setAttribute("hidden", "true");
|
||||
document
|
||||
.getElementById("contextmenu-disable")
|
||||
.setAttribute("hidden", "true");
|
||||
return;
|
||||
}
|
||||
|
||||
let enabled = this.target.getAttribute("isDisabled") != "true";
|
||||
if (enabled) {
|
||||
document
|
||||
.getElementById("contextmenu-enable")
|
||||
.setAttribute("hidden", "true");
|
||||
document.getElementById("contextmenu-disable").removeAttribute("hidden");
|
||||
} else {
|
||||
document.getElementById("contextmenu-enable").removeAttribute("hidden");
|
||||
document
|
||||
.getElementById("contextmenu-disable")
|
||||
.setAttribute("hidden", "true");
|
||||
}
|
||||
},
|
||||
|
||||
enable: function(event) {
|
||||
Addons.setEnabled(true, this.target.addon);
|
||||
this.target = null;
|
||||
},
|
||||
|
||||
disable: function(event) {
|
||||
Addons.setEnabled(false, this.target.addon);
|
||||
this.target = null;
|
||||
},
|
||||
|
||||
uninstall: function(event) {
|
||||
Addons.uninstall(this.target.addon);
|
||||
this.target = null;
|
||||
},
|
||||
};
|
||||
|
||||
function sendEMPong() {
|
||||
Services.obs.notifyObservers(window, "EM-pong");
|
||||
}
|
||||
|
||||
async function init() {
|
||||
window.addEventListener("popstate", onPopState);
|
||||
|
||||
AddonManager.addInstallListener(Addons);
|
||||
AddonManager.addAddonListener(Addons);
|
||||
|
||||
await Addons.init();
|
||||
showAddons();
|
||||
ContextMenus.init();
|
||||
|
||||
Services.obs.addObserver(sendEMPong, "EM-ping");
|
||||
|
||||
// The addons list has been loaded and rendered, send a notification
|
||||
// if the openOptionsPage is waiting to be able to select an addon details page.
|
||||
Services.obs.notifyObservers(window, "EM-loaded");
|
||||
}
|
||||
|
||||
function uninit() {
|
||||
AddonManager.removeInstallListener(Addons);
|
||||
AddonManager.removeAddonListener(Addons);
|
||||
|
||||
Services.obs.removeObserver(sendEMPong, "EM-ping");
|
||||
}
|
||||
|
||||
function openLink(url) {
|
||||
let BrowserApp = gChromeWin.BrowserApp;
|
||||
BrowserApp.addTab(url, {
|
||||
selected: true,
|
||||
parentId: BrowserApp.selectedTab.id,
|
||||
});
|
||||
}
|
||||
|
||||
function openOptionsInTab(url) {
|
||||
let BrowserApp = gChromeWin.BrowserApp;
|
||||
BrowserApp.selectOrAddTab(url, {
|
||||
startsWith: true,
|
||||
selected: true,
|
||||
parentId: BrowserApp.selectedTab.id,
|
||||
});
|
||||
}
|
||||
|
||||
function onPopState(aEvent) {
|
||||
// Called when back/forward is used to change the state of the page
|
||||
if (aEvent.state) {
|
||||
// Show the detail page for an addon
|
||||
const listItem = Addons._getElementForAddon(aEvent.state.id);
|
||||
if (listItem) {
|
||||
Addons.showDetails(listItem);
|
||||
} else {
|
||||
// If the addon doesn't exist anymore, go back in the history.
|
||||
history.back();
|
||||
}
|
||||
} else {
|
||||
// Clear any previous detail addon
|
||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
||||
detailItem.addon = null;
|
||||
|
||||
showAddons();
|
||||
}
|
||||
}
|
||||
|
||||
function showAddonDetails(addonId) {
|
||||
const listItem = Addons._getElementForAddon(addonId);
|
||||
if (listItem) {
|
||||
Addons.showDetails(listItem);
|
||||
history.pushState({ id: addonId }, document.title);
|
||||
} else {
|
||||
throw new Error(`Addon not found: ${addonId}`);
|
||||
}
|
||||
}
|
||||
|
||||
function showAddons() {
|
||||
// Hide the addon options and show the addons list
|
||||
let details = document.querySelector("#addons-details");
|
||||
details.classList.add("hidden");
|
||||
let list = document.querySelector("#addons-list");
|
||||
list.classList.remove("hidden");
|
||||
document.documentElement.removeAttribute("details");
|
||||
|
||||
// Clean the optionsBox content when switching to the add-ons list view.
|
||||
let optionsBox = document.querySelector(
|
||||
"#addons-details > .addon-item .options-box"
|
||||
);
|
||||
optionsBox.innerHTML = "";
|
||||
}
|
||||
|
||||
function showAddonOptions() {
|
||||
// Hide the addon list and show the addon options
|
||||
let list = document.querySelector("#addons-list");
|
||||
list.classList.add("hidden");
|
||||
let details = document.querySelector("#addons-details");
|
||||
details.classList.remove("hidden");
|
||||
document.documentElement.setAttribute("details", "true");
|
||||
}
|
||||
|
||||
var Addons = {
|
||||
_restartCount: 0,
|
||||
|
||||
_createItem: function _createItem(aAddon) {
|
||||
let outer = document.createElement("div");
|
||||
outer.setAttribute("addonID", aAddon.id);
|
||||
outer.className = "addon-item list-item";
|
||||
outer.setAttribute("role", "button");
|
||||
outer.setAttribute("contextmenu", "addonmenu");
|
||||
outer.addEventListener(
|
||||
"click",
|
||||
() => {
|
||||
this.showDetails(outer);
|
||||
history.pushState({ id: aAddon.id }, document.title);
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
let img = document.createElement("img");
|
||||
img.className = "icon";
|
||||
img.setAttribute("src", aAddon.iconURL || AMO_ICON);
|
||||
outer.appendChild(img);
|
||||
|
||||
let inner = document.createElement("div");
|
||||
inner.className = "inner";
|
||||
|
||||
let details = document.createElement("div");
|
||||
details.className = "details";
|
||||
inner.appendChild(details);
|
||||
|
||||
let titlePart = document.createElement("div");
|
||||
titlePart.textContent = aAddon.name;
|
||||
titlePart.className = "title";
|
||||
details.appendChild(titlePart);
|
||||
|
||||
let versionPart = document.createElement("div");
|
||||
versionPart.textContent = aAddon.version;
|
||||
versionPart.className = "version";
|
||||
details.appendChild(versionPart);
|
||||
|
||||
if ("description" in aAddon) {
|
||||
let descPart = document.createElement("div");
|
||||
descPart.textContent = aAddon.description;
|
||||
descPart.className = "description";
|
||||
inner.appendChild(descPart);
|
||||
}
|
||||
|
||||
outer.appendChild(inner);
|
||||
|
||||
let update = document.createElement("img");
|
||||
update.className = "update-indicator";
|
||||
update.setAttribute("src", UPDATE_INDICATOR);
|
||||
outer.appendChild(update);
|
||||
|
||||
return outer;
|
||||
},
|
||||
|
||||
_createBrowseItem: function _createBrowseItem() {
|
||||
let outer = document.createElement("div");
|
||||
outer.className = "addon-item list-item";
|
||||
outer.setAttribute("role", "button");
|
||||
outer.addEventListener(
|
||||
"click",
|
||||
function(event) {
|
||||
try {
|
||||
openLink(
|
||||
Services.urlFormatter.formatURLPref(
|
||||
"extensions.getAddons.browseAddons"
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
let img = document.createElement("img");
|
||||
img.className = "icon";
|
||||
img.setAttribute("src", AMO_ICON);
|
||||
outer.appendChild(img);
|
||||
|
||||
let inner = document.createElement("div");
|
||||
inner.className = "inner";
|
||||
|
||||
let title = document.createElement("div");
|
||||
title.id = "browse-title";
|
||||
title.className = "title";
|
||||
title.textContent = this._getAmoTitle();
|
||||
inner.appendChild(title);
|
||||
|
||||
outer.appendChild(inner);
|
||||
return outer;
|
||||
},
|
||||
|
||||
// Ensure we get a localized string by using the previous title as a fallback
|
||||
// if the new one has not yet been translated.
|
||||
_getAmoTitle: function _getAmoTitle() {
|
||||
const initialTitleUS = "Browse all Firefox Add-ons";
|
||||
const updatedTitleUS = "Browse Firefox’s Recommended Extensions";
|
||||
const initialTitleLocalized = gStringBundle.GetStringFromName(
|
||||
"addons.browseAll"
|
||||
);
|
||||
const updatedTitleLocalized = gStringBundle.GetStringFromName(
|
||||
"addons.browseRecommended"
|
||||
);
|
||||
let title = initialTitleLocalized;
|
||||
|
||||
const titleWasLocalized = updatedTitleLocalized !== updatedTitleUS;
|
||||
const localeIsDefaultUS =
|
||||
updatedTitleLocalized === updatedTitleUS &&
|
||||
initialTitleLocalized === initialTitleUS;
|
||||
|
||||
if (titleWasLocalized || localeIsDefaultUS) {
|
||||
title = updatedTitleLocalized;
|
||||
}
|
||||
|
||||
EventDispatcher.instance.dispatch("about:addons", { amoTitle: title });
|
||||
return title;
|
||||
},
|
||||
|
||||
_createItemForAddon: function _createItemForAddon(aAddon) {
|
||||
let opType = this._getOpTypeForOperations(aAddon.pendingOperations);
|
||||
let hasUpdate = this._addonHasUpdate(aAddon);
|
||||
|
||||
let optionsURL = aAddon.optionsURL || "";
|
||||
|
||||
let blocked = "";
|
||||
switch (aAddon.blocklistState) {
|
||||
case Ci.nsIBlocklistService.STATE_BLOCKED:
|
||||
blocked = "blocked";
|
||||
break;
|
||||
case Ci.nsIBlocklistService.STATE_SOFTBLOCKED:
|
||||
blocked = "softBlocked";
|
||||
break;
|
||||
case Ci.nsIBlocklistService.STATE_OUTDATED:
|
||||
blocked = "outdated";
|
||||
break;
|
||||
}
|
||||
|
||||
let item = this._createItem(aAddon);
|
||||
item.setAttribute("isDisabled", !aAddon.isActive);
|
||||
item.setAttribute(
|
||||
"isUnsigned",
|
||||
aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING
|
||||
);
|
||||
item.setAttribute("opType", opType);
|
||||
if (blocked) {
|
||||
item.setAttribute("blockedStatus", blocked);
|
||||
}
|
||||
item.setAttribute("optionsURL", optionsURL);
|
||||
item.setAttribute("hasUpdate", hasUpdate);
|
||||
item.addon = aAddon;
|
||||
|
||||
return item;
|
||||
},
|
||||
|
||||
_getElementForAddon: function(aKey) {
|
||||
let list = document.getElementById("addons-list");
|
||||
let element = list.querySelector('div[addonID="' + CSS.escape(aKey) + '"]');
|
||||
return element;
|
||||
},
|
||||
|
||||
_addonHasUpdate(addon) {
|
||||
return gChromeWin.ExtensionPermissions.updates.has(addon.id);
|
||||
},
|
||||
|
||||
init: async function init() {
|
||||
const aAddons = await AddonManager.getAllAddons();
|
||||
|
||||
// Clear all content before filling the addons
|
||||
let list = document.getElementById("addons-list");
|
||||
list.innerHTML = "";
|
||||
|
||||
aAddons.sort(function(a, b) {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
for (let i = 0; i < aAddons.length; i++) {
|
||||
// Don't create item for system add-ons.
|
||||
if (aAddons[i].isSystem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let item = this._createItemForAddon(aAddons[i]);
|
||||
list.appendChild(item);
|
||||
}
|
||||
|
||||
// Add a "Browse all Firefox Add-ons" item to the bottom of the list.
|
||||
let browseItem = this._createBrowseItem();
|
||||
list.appendChild(browseItem);
|
||||
|
||||
document
|
||||
.getElementById("update-btn")
|
||||
.addEventListener("click", Addons.updateCurrent.bind(this));
|
||||
document
|
||||
.getElementById("uninstall-btn")
|
||||
.addEventListener("click", Addons.uninstallCurrent.bind(this));
|
||||
document
|
||||
.getElementById("cancel-btn")
|
||||
.addEventListener("click", Addons.cancelUninstall.bind(this));
|
||||
document
|
||||
.getElementById("disable-btn")
|
||||
.addEventListener("click", Addons.disable.bind(this));
|
||||
document
|
||||
.getElementById("enable-btn")
|
||||
.addEventListener("click", Addons.enable.bind(this));
|
||||
|
||||
document
|
||||
.getElementById("unsigned-learn-more")
|
||||
.addEventListener("click", function() {
|
||||
openLink(
|
||||
Services.urlFormatter.formatURLPref("app.support.baseURL") +
|
||||
"unsigned-addons"
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
_getOpTypeForOperations: function _getOpTypeForOperations(aOperations) {
|
||||
if (aOperations & AddonManager.PENDING_UNINSTALL) {
|
||||
return "needs-uninstall";
|
||||
}
|
||||
if (aOperations & AddonManager.PENDING_ENABLE) {
|
||||
return "needs-enable";
|
||||
}
|
||||
if (aOperations & AddonManager.PENDING_DISABLE) {
|
||||
return "needs-disable";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
showDetails: function showDetails(aListItem) {
|
||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
||||
detailItem.setAttribute("isDisabled", aListItem.getAttribute("isDisabled"));
|
||||
detailItem.setAttribute("isUnsigned", aListItem.getAttribute("isUnsigned"));
|
||||
detailItem.setAttribute("opType", aListItem.getAttribute("opType"));
|
||||
detailItem.setAttribute("optionsURL", aListItem.getAttribute("optionsURL"));
|
||||
let addon = (detailItem.addon = aListItem.addon);
|
||||
|
||||
let favicon = document.querySelector("#addons-details > .addon-item .icon");
|
||||
favicon.setAttribute("src", addon.iconURL || AMO_ICON);
|
||||
|
||||
detailItem.querySelector(".title").textContent = addon.name;
|
||||
detailItem.querySelector(".version").textContent = addon.version;
|
||||
detailItem.querySelector(".description-full").textContent =
|
||||
addon.description;
|
||||
detailItem.querySelector(
|
||||
".status-uninstalled"
|
||||
).textContent = gStringBundle.formatStringFromName(
|
||||
"addonStatus.uninstalled",
|
||||
[addon.name]
|
||||
);
|
||||
|
||||
let updateBtn = document.getElementById("update-btn");
|
||||
if (this._addonHasUpdate(addon)) {
|
||||
updateBtn.removeAttribute("hidden");
|
||||
} else {
|
||||
updateBtn.setAttribute("hidden", true);
|
||||
}
|
||||
|
||||
let enableBtn = document.getElementById("enable-btn");
|
||||
if (addon.appDisabled) {
|
||||
enableBtn.setAttribute("disabled", "true");
|
||||
} else {
|
||||
enableBtn.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
let uninstallBtn = document.getElementById("uninstall-btn");
|
||||
if (addon.scope == AddonManager.SCOPE_APPLICATION) {
|
||||
uninstallBtn.setAttribute("disabled", "true");
|
||||
} else {
|
||||
uninstallBtn.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
let addonItem = document.querySelector("#addons-details > .addon-item");
|
||||
let optionsBox = addonItem.querySelector(".options-box");
|
||||
let optionsURL = aListItem.getAttribute("optionsURL");
|
||||
|
||||
// Always clean the options content before rendering the options of the
|
||||
// newly selected extension.
|
||||
optionsBox.innerHTML = "";
|
||||
|
||||
switch (parseInt(addon.optionsType)) {
|
||||
case AddonManager.OPTIONS_TYPE_INLINE_BROWSER:
|
||||
// Allow the options to use all the available width space.
|
||||
optionsBox.classList.remove("inner");
|
||||
|
||||
this.createWebExtensionOptions(optionsBox, addon, addonItem);
|
||||
break;
|
||||
case AddonManager.OPTIONS_TYPE_TAB:
|
||||
// Keep the usual layout for any options related the legacy (or system) add-ons
|
||||
// when the options are opened in a new tab from a single button in the addon
|
||||
// details page.
|
||||
optionsBox.classList.add("inner");
|
||||
|
||||
this.createOptionsInTabButton(optionsBox, addon, addonItem);
|
||||
break;
|
||||
}
|
||||
|
||||
showAddonOptions();
|
||||
},
|
||||
|
||||
createOptionsInTabButton: function(destination, addon, detailItem) {
|
||||
let frame = destination.querySelector("iframe#addon-options");
|
||||
let button = destination.querySelector("button#open-addon-options");
|
||||
|
||||
if (frame) {
|
||||
// Remove any existent options frame (e.g. when the addon updates
|
||||
// contains the open_in_tab options for the first time).
|
||||
|
||||
frame.remove();
|
||||
}
|
||||
|
||||
if (!button) {
|
||||
button = document.createElement("button");
|
||||
button.setAttribute("id", "open-addon-options");
|
||||
button.textContent = gStringBundle.GetStringFromName("addon.options");
|
||||
destination.appendChild(button);
|
||||
}
|
||||
|
||||
button.onclick = async () => {
|
||||
if (addon.isWebExtension) {
|
||||
// WebExtensions are loaded asynchronously and the optionsURL
|
||||
// may not be available until the addon has been started.
|
||||
await addon.startupPromise;
|
||||
}
|
||||
|
||||
const { optionsURL } = addon;
|
||||
openOptionsInTab(optionsURL);
|
||||
};
|
||||
|
||||
// Ensure that the Addon Options are visible (the options box will be hidden if the optionsURL
|
||||
// attribute is an empty string, which happens when a WebExtensions is still loading).
|
||||
detailItem.removeAttribute("optionsURL");
|
||||
},
|
||||
|
||||
createWebExtensionOptions: async function(destination, addon, detailItem) {
|
||||
// WebExtensions are loaded asynchronously and the optionsURL
|
||||
// may not be available until the addon has been started.
|
||||
await addon.startupPromise;
|
||||
|
||||
const { optionsURL, optionsBrowserStyle } = addon;
|
||||
let frame = destination.querySelector("iframe#addon-options");
|
||||
|
||||
if (!frame) {
|
||||
let originalHeight;
|
||||
frame = document.createElement("iframe");
|
||||
frame.setAttribute("id", "addon-options");
|
||||
frame.setAttribute("mozbrowser", "true");
|
||||
frame.setAttribute("style", "width: 100%; overflow: hidden;");
|
||||
|
||||
// Adjust iframe height to the iframe content (also between navigation of multiple options
|
||||
// files).
|
||||
frame.onload = evt => {
|
||||
if (evt.target !== frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { document } = frame.contentWindow;
|
||||
const bodyScrollHeight = document.body && document.body.scrollHeight;
|
||||
const documentScrollHeight = document.documentElement.scrollHeight;
|
||||
|
||||
// Set the iframe height to the maximum between the body and the document
|
||||
// scrollHeight values.
|
||||
frame.style.height =
|
||||
Math.max(bodyScrollHeight, documentScrollHeight) + "px";
|
||||
|
||||
// Restore the original iframe height between option page loads,
|
||||
// so that we don't force the new document to have the same size
|
||||
// of the previosuly loaded option page.
|
||||
frame.contentWindow.addEventListener(
|
||||
"unload",
|
||||
() => {
|
||||
frame.style.height = originalHeight + "px";
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
};
|
||||
|
||||
destination.appendChild(frame);
|
||||
originalHeight = frame.getBoundingClientRect().height;
|
||||
}
|
||||
|
||||
// Loading the URL this way prevents the native back
|
||||
// button from applying to the iframe.
|
||||
frame.contentWindow.location.replace(optionsURL);
|
||||
|
||||
// Ensure that the Addon Options are visible (the options box will be hidden if the optionsURL
|
||||
// attribute is an empty string, which happens when a WebExtensions is still loading).
|
||||
detailItem.removeAttribute("optionsURL");
|
||||
},
|
||||
|
||||
setEnabled: function setEnabled(aValue, aAddon) {
|
||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
||||
let addon = aAddon || detailItem.addon;
|
||||
if (!addon) {
|
||||
return;
|
||||
}
|
||||
|
||||
let listItem = this._getElementForAddon(addon.id);
|
||||
|
||||
function setDisabled(addon, value) {
|
||||
if (value) {
|
||||
return addon.disable();
|
||||
}
|
||||
return addon.enable();
|
||||
}
|
||||
|
||||
function updateOtherThemeStateInUI(item) {
|
||||
if (aValue) {
|
||||
// Mark the previously enabled theme as disabled.
|
||||
if (item.addon.isActive) {
|
||||
item.setAttribute("isDisabled", true);
|
||||
return true;
|
||||
}
|
||||
// The current theme is being disabled - enable the default theme.
|
||||
} else if (item.addon.id == "default-theme@mozilla.org") {
|
||||
item.removeAttribute("isDisabled");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
let opType;
|
||||
if (addon.type == "theme") {
|
||||
// Themes take care of themselves to make sure only one is active at the
|
||||
// same time, but we need to fix up the state of other themes in the UI.
|
||||
let list = document.getElementById("addons-list");
|
||||
let item = list.firstElementChild;
|
||||
while (item) {
|
||||
if (
|
||||
item.addon &&
|
||||
item.addon.type == "theme" &&
|
||||
updateOtherThemeStateInUI(item)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
item = item.nextSibling;
|
||||
}
|
||||
setDisabled(addon, !aValue);
|
||||
} else if (addon.type == "locale") {
|
||||
setDisabled(addon, !aValue);
|
||||
} else {
|
||||
setDisabled(addon, !aValue);
|
||||
opType = this._getOpTypeForOperations(addon.pendingOperations);
|
||||
|
||||
if (
|
||||
addon.pendingOperations & AddonManager.PENDING_ENABLE ||
|
||||
addon.pendingOperations & AddonManager.PENDING_DISABLE
|
||||
) {
|
||||
this.showRestart();
|
||||
} else if (
|
||||
listItem &&
|
||||
/needs-(enable|disable)/.test(listItem.getAttribute("opType"))
|
||||
) {
|
||||
this.hideRestart();
|
||||
}
|
||||
}
|
||||
|
||||
if (addon == detailItem.addon) {
|
||||
detailItem.setAttribute("isDisabled", !aValue);
|
||||
if (opType) {
|
||||
detailItem.setAttribute("opType", opType);
|
||||
} else {
|
||||
detailItem.removeAttribute("opType");
|
||||
}
|
||||
|
||||
// Remove any addon options iframe if the currently selected addon has been disabled.
|
||||
if (!aValue) {
|
||||
const addonOptionsIframe = document.querySelector("#addon-options");
|
||||
if (addonOptionsIframe) {
|
||||
addonOptionsIframe.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sync to the list item
|
||||
if (listItem) {
|
||||
listItem.setAttribute("isDisabled", !aValue);
|
||||
if (opType) {
|
||||
listItem.setAttribute("opType", opType);
|
||||
} else {
|
||||
listItem.removeAttribute("opType");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
enable: function enable() {
|
||||
this.setEnabled(true);
|
||||
},
|
||||
|
||||
disable: function disable() {
|
||||
this.setEnabled(false);
|
||||
},
|
||||
|
||||
updateCurrent() {
|
||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
||||
|
||||
let addon = detailItem.addon;
|
||||
if (!addon) {
|
||||
return;
|
||||
}
|
||||
|
||||
gChromeWin.ExtensionPermissions.applyUpdate(addon.id);
|
||||
},
|
||||
|
||||
uninstallCurrent: function uninstallCurrent() {
|
||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
||||
|
||||
let addon = detailItem.addon;
|
||||
if (!addon) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uninstall(addon);
|
||||
},
|
||||
|
||||
uninstall: function uninstall(aAddon) {
|
||||
if (!aAddon) {
|
||||
return;
|
||||
}
|
||||
|
||||
let listItem = this._getElementForAddon(aAddon.id);
|
||||
aAddon.uninstall();
|
||||
|
||||
if (aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL) {
|
||||
this.showRestart();
|
||||
|
||||
// A disabled addon doesn't need a restart so it has no pending ops and
|
||||
// can't be cancelled
|
||||
let opType = this._getOpTypeForOperations(aAddon.pendingOperations);
|
||||
if (!aAddon.isActive && opType == "") {
|
||||
opType = "needs-uninstall";
|
||||
}
|
||||
|
||||
detailItem.setAttribute("opType", opType);
|
||||
listItem.setAttribute("opType", opType);
|
||||
}
|
||||
},
|
||||
|
||||
cancelUninstall: function ev_cancelUninstall() {
|
||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
||||
let addon = detailItem.addon;
|
||||
if (!addon) {
|
||||
return;
|
||||
}
|
||||
|
||||
addon.cancelUninstall();
|
||||
this.hideRestart();
|
||||
|
||||
let opType = this._getOpTypeForOperations(addon.pendingOperations);
|
||||
detailItem.setAttribute("opType", opType);
|
||||
|
||||
let listItem = this._getElementForAddon(addon.id);
|
||||
listItem.setAttribute("opType", opType);
|
||||
},
|
||||
|
||||
showRestart: function showRestart() {
|
||||
this._restartCount++;
|
||||
gChromeWin.XPInstallObserver.showRestartPrompt();
|
||||
},
|
||||
|
||||
hideRestart: function hideRestart() {
|
||||
this._restartCount--;
|
||||
if (this._restartCount == 0) {
|
||||
gChromeWin.XPInstallObserver.hideRestartPrompt();
|
||||
}
|
||||
},
|
||||
|
||||
onEnabled: function(aAddon) {
|
||||
let listItem = this._getElementForAddon(aAddon.id);
|
||||
if (!listItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reload the details to pick up any options now that it's enabled.
|
||||
listItem.setAttribute("optionsURL", aAddon.optionsURL || "");
|
||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
||||
if (aAddon == detailItem.addon) {
|
||||
this.showDetails(listItem);
|
||||
}
|
||||
},
|
||||
|
||||
onInstallEnded: function(aInstall, aAddon) {
|
||||
let needsRestart = false;
|
||||
if (
|
||||
aInstall.existingAddon &&
|
||||
aInstall.existingAddon.pendingOperations & AddonManager.PENDING_UPGRADE
|
||||
) {
|
||||
needsRestart = true;
|
||||
} else if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL) {
|
||||
needsRestart = true;
|
||||
}
|
||||
|
||||
let list = document.getElementById("addons-list");
|
||||
let element = this._getElementForAddon(aAddon.id);
|
||||
if (!element) {
|
||||
element = this._createItemForAddon(aAddon);
|
||||
list.insertBefore(element, list.firstElementChild);
|
||||
}
|
||||
|
||||
if (needsRestart) {
|
||||
element.setAttribute("opType", "needs-restart");
|
||||
}
|
||||
},
|
||||
|
||||
onInstalled: function(aAddon) {
|
||||
let list = document.getElementById("addons-list");
|
||||
let element = this._getElementForAddon(aAddon.id);
|
||||
if (element) {
|
||||
// Upgrade of an existing addon, update version and description in
|
||||
// list item and detail view, plus indicators about a pending update.
|
||||
element.querySelector(".version").textContent = aAddon.version;
|
||||
|
||||
let desc = element.querySelector(".description");
|
||||
if (desc) {
|
||||
desc.textContent = aAddon.description;
|
||||
}
|
||||
|
||||
element.setAttribute("hasUpdate", false);
|
||||
document.getElementById("update-btn").setAttribute("hidden", true);
|
||||
|
||||
element = document.querySelector("#addons-details > .addon-item");
|
||||
if (element.addon && element.addon.id == aAddon.id) {
|
||||
element.querySelector(".version").textContent = aAddon.version;
|
||||
element.querySelector(".description-full").textContent =
|
||||
aAddon.description;
|
||||
}
|
||||
} else {
|
||||
element = this._createItemForAddon(aAddon);
|
||||
|
||||
// Themes aren't considered active on install, so set existing as disabled, and new one enabled.
|
||||
if (aAddon.type == "theme") {
|
||||
let item = list.firstElementChild;
|
||||
while (item) {
|
||||
if (item.addon && item.addon.type == "theme") {
|
||||
item.setAttribute("isDisabled", true);
|
||||
}
|
||||
item = item.nextSibling;
|
||||
}
|
||||
element.setAttribute("isDisabled", false);
|
||||
}
|
||||
|
||||
list.insertBefore(element, list.firstElementChild);
|
||||
}
|
||||
},
|
||||
|
||||
onUninstalled: function(aAddon) {
|
||||
let list = document.getElementById("addons-list");
|
||||
let element = this._getElementForAddon(aAddon.id);
|
||||
list.removeChild(element);
|
||||
|
||||
// Go back if we're in the detail view of the add-on that was uninstalled.
|
||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
||||
if (detailItem.addon.id == aAddon.id) {
|
||||
history.back();
|
||||
}
|
||||
},
|
||||
|
||||
onInstallFailed: function(aInstall) {},
|
||||
|
||||
onDownloadProgress: function xpidm_onDownloadProgress(aInstall) {},
|
||||
|
||||
onDownloadFailed: function(aInstall) {},
|
||||
|
||||
onDownloadCancelled: function(aInstall) {},
|
||||
};
|
||||
|
||||
window.addEventListener("load", init);
|
||||
window.addEventListener("unload", uninit);
|
@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
|
||||
%globalDTD;
|
||||
<!ENTITY % aboutDTD SYSTEM "chrome://browser/locale/aboutAddons.dtd" >
|
||||
%aboutDTD;
|
||||
]>
|
||||
|
||||
<!-- 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 xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&aboutAddons.title2;</title>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutAddons.css" type="text/css"/>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<menu type="context" id="addonmenu">
|
||||
<menuitem id="contextmenu-enable" label="&addonAction.enable;"></menuitem>
|
||||
<menuitem id="contextmenu-disable" label="&addonAction.disable;" ></menuitem>
|
||||
<menuitem id="contextmenu-uninstall" label="&addonAction.uninstall;" ></menuitem>
|
||||
</menu>
|
||||
|
||||
<div id="addons-header" class="header">
|
||||
<div>&aboutAddons.header2;</div>
|
||||
</div>
|
||||
<div id="addons-list" class="list hidden">
|
||||
</div>
|
||||
|
||||
<div id="addons-details" class="list hidden">
|
||||
<div class="addon-item list-item">
|
||||
<img class="icon"/>
|
||||
<div class="inner">
|
||||
<div class="details">
|
||||
<div class="title"></div><div class="version"></div>
|
||||
</div>
|
||||
<div class="description-full"></div>
|
||||
</div>
|
||||
<div class="warn-unsigned">&addonUnsigned.message; <a id="unsigned-learn-more">&addonUnsigned.learnMore;</a></div>
|
||||
<div class="options-box"></div>
|
||||
<div class="status status-uninstalled show-on-uninstall"></div>
|
||||
<div class="buttons">
|
||||
<button id="update-btn" class="show-on-update">&addonAction.update;</button>
|
||||
<button id="enable-btn" class="show-on-disable hide-on-enable hide-on-uninstall" >&addonAction.enable;</button>
|
||||
<button id="disable-btn" class="show-on-enable hide-on-disable hide-on-uninstall" >&addonAction.disable;</button>
|
||||
<button id="uninstall-btn" class="hide-on-uninstall" >&addonAction.uninstall;</button>
|
||||
<button id="cancel-btn" class="show-on-uninstall" >&addonAction.undo;</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/aboutAddons.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,152 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % globalDTD
|
||||
SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % certerrorDTD
|
||||
SYSTEM "chrome://browser/locale/aboutCertError.dtd">
|
||||
%certerrorDTD;
|
||||
]>
|
||||
|
||||
<!-- 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 xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&certerror.pagetitle;</title>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=false" />
|
||||
<link rel="stylesheet" href="chrome://browser/skin/netError.css" media="all" />
|
||||
<!-- This page currently uses the same favicon as neterror.xhtml.
|
||||
If the location of the favicon is changed for both pages, the
|
||||
FAVICON_ERRORPAGE_URL symbol in toolkit/components/places/src/nsFaviconService.h
|
||||
should be updated. If this page starts using a different favicon
|
||||
than neterrorm nsFaviconService->SetAndLoadFaviconForPage
|
||||
should be updated to ignore this one as well. -->
|
||||
<link rel="icon" type="image/png" id="favicon" sizes="64x64" href="chrome://browser/skin/images/certerror-warning.png"/>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
// Error url MUST be formatted like this:
|
||||
// about:certerror?e=error&u=url&d=desc
|
||||
|
||||
// Note that this file uses document.documentURI to get
|
||||
// the URL (with the format from above). This is because
|
||||
// document.location.href gets the current URI off the docshell,
|
||||
// which is the URL displayed in the location bar, i.e.
|
||||
// the URI that the user attempted to load.
|
||||
|
||||
function getCSSClass() {
|
||||
var url = document.documentURI;
|
||||
var matches = url.match(/s\=([^&]+)\&/);
|
||||
// s is optional, if no match just return nothing
|
||||
if (!matches || matches.length < 2)
|
||||
return "";
|
||||
|
||||
// parenthetical match is the second entry
|
||||
return decodeURIComponent(matches[1]);
|
||||
}
|
||||
|
||||
function initPage() {
|
||||
// Replace the "#1" string in the intro with the hostname. Trickier
|
||||
// than it might seem since we want to preserve the <b> tags, but
|
||||
// not allow for any injection by just using innerHTML. Instead,
|
||||
// just find the right target text node.
|
||||
var intro = document.getElementById("introContentP1");
|
||||
function replaceWithHost(node) {
|
||||
if (node.textContent == "#1")
|
||||
node.textContent = location.host;
|
||||
else
|
||||
for (var i = 0; i < node.childNodes.length; i++)
|
||||
replaceWithHost(node.childNodes[i]);
|
||||
}
|
||||
replaceWithHost(intro);
|
||||
|
||||
if (getCSSClass() == "expertBadCert") {
|
||||
toggle("technicalContent");
|
||||
toggle("expertContent");
|
||||
}
|
||||
|
||||
// Disallow overrides if this is a Strict-Transport-Security
|
||||
// host and the cert is bad (STS Spec section 7.3) or if the
|
||||
// certerror is in a frame (bug 633691).
|
||||
if (getCSSClass() == "badStsCert" || window != top)
|
||||
document.getElementById("expertContent").setAttribute("hidden", "true");
|
||||
|
||||
var event = new CustomEvent("AboutCertErrorLoad", {bubbles: true});
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function createLink(el, id, text) {
|
||||
var anchorEl = document.createElement("a");
|
||||
anchorEl.setAttribute("id", id);
|
||||
anchorEl.setAttribute("title", text);
|
||||
anchorEl.appendChild(document.createTextNode(text));
|
||||
el.appendChild(anchorEl);
|
||||
}
|
||||
|
||||
function toggle(id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el.hasAttribute("collapsed"))
|
||||
el.removeAttribute("collapsed");
|
||||
else
|
||||
el.setAttribute("collapsed", true);
|
||||
}
|
||||
]]></script>
|
||||
</head>
|
||||
|
||||
<body id="errorPage" class="certerror" dir="&locale.dir;">
|
||||
|
||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
||||
<div id="errorPageContainer">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div id="errorTitle">
|
||||
<h1 class="errorTitleText">&certerror.longpagetitle;</h1>
|
||||
</div>
|
||||
|
||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
||||
<div id="errorLongContent">
|
||||
<div id="introContent">
|
||||
<p id="introContentP1">&certerror.introPara1;</p>
|
||||
</div>
|
||||
|
||||
<div id="whatShouldIDoContent">
|
||||
<h2>&certerror.whatShouldIDo.heading;</h2>
|
||||
<div>
|
||||
<p id="whatShouldIDoContentText">&certerror.whatShouldIDo.content;</p>
|
||||
<button id="getMeOutOfHereButton">&certerror.getMeOutOfHere.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- The following sections can be unhidden by default by setting the
|
||||
"browser.xul.error_pages.expert_bad_cert" pref to true -->
|
||||
<div id="technicalContent" collapsed="true">
|
||||
<h2 class="expander" onclick="toggle('technicalContent');" id="technicalContentHeading">&certerror.technical.heading;</h2>
|
||||
<p id="technicalContentText"/>
|
||||
</div>
|
||||
|
||||
<div id="expertContent" collapsed="true">
|
||||
<h2 class="expander" onclick="toggle('expertContent');" id="expertContentHeading">&certerror.expert.heading;</h2>
|
||||
<div>
|
||||
<p>&certerror.expert.content;</p>
|
||||
<p>&certerror.expert.contentPara2;</p>
|
||||
<button id="temporaryExceptionButton">&certerror.addTemporaryException.label;</button>
|
||||
<button id="permanentExceptionButton">&certerror.addPermanentException.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
- Note: It is important to run the script this way, instead of using
|
||||
- an onload handler. This is because error pages are loaded as
|
||||
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
|
||||
-->
|
||||
<script type="application/javascript">initPage();</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,427 +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/. */
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Downloads",
|
||||
"resource://gre/modules/Downloads.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"DownloadUtils",
|
||||
"resource://gre/modules/DownloadUtils.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"EventDispatcher",
|
||||
"resource://gre/modules/Messaging.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"PluralForm",
|
||||
"resource://gre/modules/PluralForm.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Services",
|
||||
"resource://gre/modules/Services.jsm"
|
||||
);
|
||||
|
||||
var gStrings = Services.strings.createBundle(
|
||||
"chrome://browser/locale/aboutDownloads.properties"
|
||||
);
|
||||
XPCOMUtils.defineLazyGetter(this, "strings", () =>
|
||||
Services.strings.createBundle(
|
||||
"chrome://browser/locale/aboutDownloads.properties"
|
||||
)
|
||||
);
|
||||
|
||||
function deleteDownload(download) {
|
||||
download.finalize(true).catch(Cu.reportError);
|
||||
OS.File.remove(download.target.path).catch(ex => {
|
||||
if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var contextMenu = {
|
||||
_items: [],
|
||||
_targetDownload: null,
|
||||
|
||||
init: function() {
|
||||
let element = document.getElementById("downloadmenu");
|
||||
element.addEventListener(
|
||||
"click",
|
||||
event => (event.download = this._targetDownload),
|
||||
true
|
||||
);
|
||||
this._items = [
|
||||
new ContextMenuItem(
|
||||
"open",
|
||||
download => download.succeeded,
|
||||
download => download.launch().catch(Cu.reportError)
|
||||
),
|
||||
new ContextMenuItem(
|
||||
"retry",
|
||||
download =>
|
||||
download.error || (download.canceled && !download.hasPartialData),
|
||||
download => download.start().catch(Cu.reportError)
|
||||
),
|
||||
new ContextMenuItem(
|
||||
"remove",
|
||||
download => download.stopped,
|
||||
download => {
|
||||
Downloads.getList(Downloads.ALL)
|
||||
.then(list => list.remove(download))
|
||||
.catch(Cu.reportError);
|
||||
deleteDownload(download);
|
||||
}
|
||||
),
|
||||
new ContextMenuItem(
|
||||
"pause",
|
||||
download => !download.stopped && download.hasPartialData,
|
||||
download => download.cancel().catch(Cu.reportError)
|
||||
),
|
||||
new ContextMenuItem(
|
||||
"resume",
|
||||
download => download.canceled && download.hasPartialData,
|
||||
download => download.start().catch(Cu.reportError)
|
||||
),
|
||||
new ContextMenuItem(
|
||||
"cancel",
|
||||
download =>
|
||||
!download.stopped || (download.canceled && download.hasPartialData),
|
||||
download => {
|
||||
download.cancel().catch(Cu.reportError);
|
||||
download.removePartialData().catch(Cu.reportError);
|
||||
}
|
||||
),
|
||||
// following menu item is a global action
|
||||
new ContextMenuItem(
|
||||
"removeall",
|
||||
() => downloadLists.finished.length > 0,
|
||||
() => downloadLists.removeFinished()
|
||||
),
|
||||
];
|
||||
},
|
||||
|
||||
addContextMenuEventListener: function(element) {
|
||||
element.addEventListener("contextmenu", this.onContextMenu.bind(this));
|
||||
},
|
||||
|
||||
onContextMenu: function(event) {
|
||||
let target = event.target;
|
||||
while (target && !target.download) {
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (!target) {
|
||||
Cu.reportError("No download found for context menu target");
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// capture the target download for menu items to use in a click event
|
||||
this._targetDownload = target.download;
|
||||
for (let item of this._items) {
|
||||
item.updateVisibility(target.download);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function ContextMenuItem(name, isVisible, action) {
|
||||
this.element = document.getElementById("contextmenu-" + name);
|
||||
this.isVisible = isVisible;
|
||||
|
||||
this.element.addEventListener("click", event => action(event.download));
|
||||
}
|
||||
|
||||
ContextMenuItem.prototype = {
|
||||
updateVisibility: function(download) {
|
||||
this.element.hidden = !this.isVisible(download);
|
||||
},
|
||||
};
|
||||
|
||||
function DownloadListView(type, listElementId) {
|
||||
this.listElement = document.getElementById(listElementId);
|
||||
contextMenu.addContextMenuEventListener(this.listElement);
|
||||
|
||||
this.items = new Map();
|
||||
|
||||
Downloads.getList(type)
|
||||
.then(list => list.addView(this))
|
||||
.catch(Cu.reportError);
|
||||
|
||||
window.addEventListener("unload", event => {
|
||||
Downloads.getList(type)
|
||||
.then(list => list.removeView(this))
|
||||
.catch(Cu.reportError);
|
||||
});
|
||||
}
|
||||
|
||||
DownloadListView.prototype = {
|
||||
get finished() {
|
||||
let finished = [];
|
||||
for (let download of this.items.keys()) {
|
||||
if (download.stopped && (!download.hasPartialData || download.error)) {
|
||||
finished.push(download);
|
||||
}
|
||||
}
|
||||
|
||||
return finished;
|
||||
},
|
||||
|
||||
insertOrMoveItem: function(item) {
|
||||
var compare = (a, b) => {
|
||||
// active downloads always before stopped downloads
|
||||
if (a.stopped != b.stopped) {
|
||||
return b.stopped ? -1 : 1;
|
||||
}
|
||||
// most recent downloads first
|
||||
return b.startTime - a.startTime;
|
||||
};
|
||||
|
||||
let insertLocation = this.listElement.firstChild;
|
||||
while (
|
||||
insertLocation &&
|
||||
compare(item.download, insertLocation.download) > 0
|
||||
) {
|
||||
insertLocation = insertLocation.nextElementSibling;
|
||||
}
|
||||
this.listElement.insertBefore(item.element, insertLocation);
|
||||
},
|
||||
|
||||
onDownloadAdded: function(download) {
|
||||
let item = new DownloadItem(download);
|
||||
this.items.set(download, item);
|
||||
this.insertOrMoveItem(item);
|
||||
},
|
||||
|
||||
onDownloadChanged: function(download) {
|
||||
let item = this.items.get(download);
|
||||
if (!item) {
|
||||
Cu.reportError("No DownloadItem found for download");
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.stateChanged) {
|
||||
this.insertOrMoveItem(item);
|
||||
}
|
||||
|
||||
item.onDownloadChanged();
|
||||
},
|
||||
|
||||
onDownloadRemoved: function(download) {
|
||||
let item = this.items.get(download);
|
||||
if (!item) {
|
||||
Cu.reportError("No DownloadItem found for download");
|
||||
return;
|
||||
}
|
||||
|
||||
this.items.delete(download);
|
||||
this.listElement.removeChild(item.element);
|
||||
|
||||
EventDispatcher.instance.sendRequest({
|
||||
type: "Download:Remove",
|
||||
path: download.target.path,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
var downloadLists = {
|
||||
init: function() {
|
||||
this.publicDownloads = new DownloadListView(
|
||||
Downloads.PUBLIC,
|
||||
"public-downloads-list"
|
||||
);
|
||||
this.privateDownloads = new DownloadListView(
|
||||
Downloads.PRIVATE,
|
||||
"private-downloads-list"
|
||||
);
|
||||
},
|
||||
|
||||
get finished() {
|
||||
return this.publicDownloads.finished.concat(this.privateDownloads.finished);
|
||||
},
|
||||
|
||||
removeFinished: function() {
|
||||
let finished = this.finished;
|
||||
if (finished.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let title = strings.GetStringFromName("downloadAction.deleteAll");
|
||||
let messageForm = strings.GetStringFromName("downloadMessage.deleteAll");
|
||||
let message = PluralForm.get(finished.length, messageForm).replace(
|
||||
"#1",
|
||||
finished.length
|
||||
);
|
||||
|
||||
if (Services.prompt.confirm(null, title, message)) {
|
||||
Downloads.getList(Downloads.ALL).then(list => {
|
||||
for (let download of finished) {
|
||||
list.remove(download).catch(Cu.reportError);
|
||||
deleteDownload(download);
|
||||
}
|
||||
}, Cu.reportError);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function DownloadItem(download) {
|
||||
this._download = download;
|
||||
this._updateFromDownload();
|
||||
|
||||
this._domain = DownloadUtils.getURIHost(download.source.url)[0];
|
||||
this._fileName = this._htmlEscape(OS.Path.basename(download.target.path));
|
||||
this._iconUrl = "moz-icon://" + this._fileName + "?size=64";
|
||||
this._startDate = this._htmlEscape(
|
||||
DownloadUtils.getReadableDates(download.startTime)[0]
|
||||
);
|
||||
|
||||
this._element = this.createElement();
|
||||
}
|
||||
|
||||
const kDownloadStatePropertyNames = [
|
||||
"stopped",
|
||||
"succeeded",
|
||||
"canceled",
|
||||
"error",
|
||||
"startTime",
|
||||
];
|
||||
|
||||
DownloadItem.prototype = {
|
||||
_htmlEscape: function(s) {
|
||||
s = s.replace(/&/g, "&");
|
||||
s = s.replace(/>/g, ">");
|
||||
s = s.replace(/</g, "<");
|
||||
s = s.replace(/"/g, """);
|
||||
s = s.replace(/'/g, "'");
|
||||
return s;
|
||||
},
|
||||
|
||||
_updateFromDownload: function() {
|
||||
this._state = {};
|
||||
kDownloadStatePropertyNames.forEach(
|
||||
name => (this._state[name] = this._download[name]),
|
||||
this
|
||||
);
|
||||
},
|
||||
|
||||
get stateChanged() {
|
||||
return kDownloadStatePropertyNames.some(
|
||||
name => this._state[name] != this._download[name],
|
||||
this
|
||||
);
|
||||
},
|
||||
|
||||
get download() {
|
||||
return this._download;
|
||||
},
|
||||
get element() {
|
||||
return this._element;
|
||||
},
|
||||
|
||||
createElement: function() {
|
||||
let template = document.getElementById("download-item");
|
||||
// TODO: use this once <template> is working
|
||||
// let element = document.importNode(template.content, true);
|
||||
|
||||
// simulate a <template> node...
|
||||
let element = template.cloneNode(true);
|
||||
element.removeAttribute("id");
|
||||
element.removeAttribute("style");
|
||||
|
||||
// launch the download if clicked
|
||||
element.addEventListener("click", this.onClick.bind(this));
|
||||
|
||||
// set download as an expando property for the context menu
|
||||
element.download = this.download;
|
||||
|
||||
// fill in template placeholders
|
||||
this.updateElement(element);
|
||||
|
||||
return element;
|
||||
},
|
||||
|
||||
updateElement: function(element) {
|
||||
element.querySelector(".date").textContent = this.startDate;
|
||||
element.querySelector(".domain").textContent = this.domain;
|
||||
element.querySelector(".icon").src = this.iconUrl;
|
||||
element.querySelector(".size").textContent = this.size;
|
||||
element.querySelector(".state").textContent = this.stateDescription;
|
||||
element.querySelector(".title").setAttribute("value", this.fileName);
|
||||
},
|
||||
|
||||
onClick: function(event) {
|
||||
if (this.download.succeeded) {
|
||||
this.download.launch().catch(Cu.reportError);
|
||||
}
|
||||
},
|
||||
|
||||
onDownloadChanged: function() {
|
||||
this._updateFromDownload();
|
||||
this.updateElement(this.element);
|
||||
},
|
||||
|
||||
// template properties below
|
||||
get domain() {
|
||||
return this._domain;
|
||||
},
|
||||
get fileName() {
|
||||
return this._fileName;
|
||||
},
|
||||
get id() {
|
||||
return this._id;
|
||||
},
|
||||
get iconUrl() {
|
||||
return this._iconUrl;
|
||||
},
|
||||
|
||||
get size() {
|
||||
if (this.download.succeeded && this.download.target.exists) {
|
||||
return DownloadUtils.convertByteUnits(this.download.target.size).join("");
|
||||
} else if (this.download.hasProgress) {
|
||||
return DownloadUtils.convertByteUnits(this.download.totalBytes).join("");
|
||||
}
|
||||
return strings.GetStringFromName("downloadState.unknownSize");
|
||||
},
|
||||
|
||||
get startDate() {
|
||||
return this._startDate;
|
||||
},
|
||||
|
||||
get stateDescription() {
|
||||
let name;
|
||||
if (this.download.error) {
|
||||
name = "downloadState.failed";
|
||||
} else if (this.download.canceled) {
|
||||
if (this.download.hasPartialData) {
|
||||
name = "downloadState.paused";
|
||||
} else {
|
||||
name = "downloadState.canceled";
|
||||
}
|
||||
} else if (!this.download.stopped) {
|
||||
if (this.download.currentBytes > 0) {
|
||||
name = "downloadState.downloading";
|
||||
} else {
|
||||
name = "downloadState.starting";
|
||||
}
|
||||
}
|
||||
|
||||
if (name) {
|
||||
return strings.GetStringFromName(name);
|
||||
}
|
||||
return "";
|
||||
},
|
||||
};
|
||||
|
||||
window.addEventListener("DOMContentLoaded", event => {
|
||||
contextMenu.init();
|
||||
downloadLists.init();
|
||||
});
|
@ -1,62 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
|
||||
%globalDTD;
|
||||
<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/aboutDownloads.dtd" >
|
||||
%downloadsDTD;
|
||||
]>
|
||||
|
||||
<!-- 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 xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<head>
|
||||
<title>&aboutDownloads.title;</title>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutDownloads.css" type="text/css"/>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<menu type="context" id="downloadmenu">
|
||||
<menuitem id="contextmenu-open" label="&aboutDownloads.open;"></menuitem>
|
||||
<menuitem id="contextmenu-retry" label="&aboutDownloads.retry;"></menuitem>
|
||||
<menuitem id="contextmenu-remove" label="&aboutDownloads.remove;"></menuitem>
|
||||
<menuitem id="contextmenu-pause" label="&aboutDownloads.pause;"></menuitem>
|
||||
<menuitem id="contextmenu-resume" label="&aboutDownloads.resume;"></menuitem>
|
||||
<menuitem id="contextmenu-cancel" label="&aboutDownloads.cancel;"></menuitem>
|
||||
<menuitem id="contextmenu-removeall" label="&aboutDownloads.removeAll;"></menuitem>
|
||||
</menu>
|
||||
|
||||
<!--template id="download-item"-->
|
||||
<li id="download-item" class="list-item" role="button" contextmenu="downloadmenu" style="display: none">
|
||||
<img class="icon" src=""/>
|
||||
<div class="details">
|
||||
<div class="row">
|
||||
<!-- This is a hack so that we can crop this label in its center -->
|
||||
<xul:label class="title" crop="center" value=""/>
|
||||
<div class="date"></div>
|
||||
</div>
|
||||
<div class="size"></div>
|
||||
<div class="domain"></div>
|
||||
<div class="state"></div>
|
||||
</div>
|
||||
</li>
|
||||
<!--/template-->
|
||||
|
||||
<div class="header">
|
||||
<div>&aboutDownloads.header;</div>
|
||||
</div>
|
||||
<ul id="private-downloads-list" class="list"></ul>
|
||||
<ul id="public-downloads-list" class="list"></ul>
|
||||
<span id="no-downloads-indicator">&aboutDownloads.empty;</span>
|
||||
<script type="application/javascript" src="chrome://browser/content/aboutDownloads.js"/>
|
||||
</body>
|
||||
</html>
|
@ -1,127 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
AndroidLog: "resource://gre/modules/AndroidLog.jsm",
|
||||
EventDispatcher: "resource://gre/modules/Messaging.jsm",
|
||||
});
|
||||
|
||||
const LOGTAG = "Experiments";
|
||||
const EXPERIMENTS_CONFIGURATION =
|
||||
"https://firefox.settings.services.mozilla.com/v1/buckets/fennec/collections/experiments/records";
|
||||
const Experiments = Services.wm.getMostRecentWindow("navigator:browser")
|
||||
.Experiments;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", initList);
|
||||
|
||||
function log(msg) {
|
||||
AndroidLog.d(LOGTAG, msg);
|
||||
}
|
||||
|
||||
function initList() {
|
||||
const list = document.getElementById("list");
|
||||
list.addEventListener("click", toggleOverride);
|
||||
|
||||
Promise.all([
|
||||
promiseEnabledExperiments(),
|
||||
promiseExperimentsConfiguration(),
|
||||
]).then(values => {
|
||||
const enabledExperiments = values[0];
|
||||
const serverConfiguration = values[1];
|
||||
|
||||
serverConfiguration.data.forEach(function(experiment) {
|
||||
try {
|
||||
let item = document.createElement("li");
|
||||
item.textContent = experiment.name;
|
||||
item.setAttribute("name", experiment.name);
|
||||
item.setAttribute(
|
||||
"isEnabled",
|
||||
enabledExperiments.includes(experiment.name)
|
||||
);
|
||||
list.appendChild(item);
|
||||
} catch (e) {
|
||||
log(`Error while setting experiments list: ${e.error}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toggleOverride(experiment) {
|
||||
const item = experiment.originalTarget;
|
||||
const name = item.getAttribute("name");
|
||||
const isEnabled = item.getAttribute("isEnabled") === "true";
|
||||
|
||||
log(`toggleOverride: ${name}`);
|
||||
|
||||
Experiments.setOverride(name, !isEnabled);
|
||||
item.setAttribute("isEnabled", !isEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of locally enabled experiments.
|
||||
*/
|
||||
function promiseEnabledExperiments() {
|
||||
log("Getting the locally enabled experiments");
|
||||
|
||||
return EventDispatcher.instance
|
||||
.sendRequestForResult({
|
||||
type: "Experiments:GetActive",
|
||||
})
|
||||
.then(experiments => {
|
||||
log("List of locally enabled experiments ready");
|
||||
return experiments;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the list of experiments from server configuration.
|
||||
*/
|
||||
function promiseExperimentsConfiguration() {
|
||||
log("Fetching server experiments");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
try {
|
||||
xhr.open("GET", EXPERIMENTS_CONFIGURATION, true);
|
||||
} catch (e) {
|
||||
reject(`Error opening request: ${e}`);
|
||||
return;
|
||||
}
|
||||
|
||||
xhr.onerror = function(e) {
|
||||
reject(`Error making request: ${e.error}`);
|
||||
};
|
||||
|
||||
xhr.onload = function(event) {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
resolve(JSON.parse(xhr.responseText));
|
||||
} catch (e) {
|
||||
const errorMessage = `Error while parsing request: ${e}`;
|
||||
log(errorMessage);
|
||||
reject(errorMessage);
|
||||
}
|
||||
} else {
|
||||
const errorMessage = `Request to ${url} returned status ${
|
||||
xhr.status
|
||||
}`;
|
||||
log(errorMessage);
|
||||
reject(errorMessage);
|
||||
}
|
||||
}
|
||||
log("Finished fetching server experiments");
|
||||
};
|
||||
|
||||
xhr.send(null);
|
||||
});
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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 xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Switchboard Experiments</title>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutExperiments.css" type="text/css"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/aboutExperiments.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ul id="list"/>
|
||||
</body>
|
||||
</html>
|
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % abouthomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
|
||||
%abouthomeDTD;
|
||||
]>
|
||||
|
||||
<!-- 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 xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&abouthome.title;</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -1,628 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
ChromeUtils.import(
|
||||
"resource://services-common/utils.js"
|
||||
); /* global: CommonUtils */
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
const { Accounts } = ChromeUtils.import("resource://gre/modules/Accounts.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(
|
||||
window,
|
||||
"gChromeWin",
|
||||
() => window.docShell.rootTreeItem.domWindow
|
||||
);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"EventDispatcher",
|
||||
"resource://gre/modules/Messaging.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Snackbars",
|
||||
"resource://gre/modules/Snackbars.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Prompt",
|
||||
"resource://gre/modules/Prompt.jsm"
|
||||
);
|
||||
|
||||
var debug = ChromeUtils.import(
|
||||
"resource://gre/modules/AndroidLog.jsm",
|
||||
{}
|
||||
).AndroidLog.d.bind(null, "AboutLogins");
|
||||
|
||||
var gStringBundle = Services.strings.createBundle(
|
||||
"chrome://browser/locale/aboutLogins.properties"
|
||||
);
|
||||
|
||||
function copyStringShowSnackbar(string, notifyString) {
|
||||
try {
|
||||
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
|
||||
Ci.nsIClipboardHelper
|
||||
);
|
||||
clipboard.copyString(string);
|
||||
Snackbars.show(notifyString, Snackbars.LENGTH_LONG);
|
||||
} catch (e) {
|
||||
debug("Error copying from about:logins");
|
||||
Snackbars.show(
|
||||
gStringBundle.GetStringFromName("loginsDetails.copyFailed"),
|
||||
Snackbars.LENGTH_LONG
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Delay filtering while typing in MS
|
||||
const FILTER_DELAY = 500;
|
||||
|
||||
var Logins = {
|
||||
_logins: [],
|
||||
_filterTimer: null,
|
||||
_selectedLogin: null,
|
||||
|
||||
// Load the logins list, displaying interstitial UI (see
|
||||
// #logins-list-loading-body) while loading. There are careful
|
||||
// jank-avoiding measures taken in this function; be careful when
|
||||
// modifying it!
|
||||
//
|
||||
// Returns a Promise that resolves to the list of logins, ordered by
|
||||
// origin.
|
||||
_promiseLogins: function() {
|
||||
let contentBody = document.getElementById("content-body");
|
||||
let emptyBody = document.getElementById("empty-body");
|
||||
let filterIcon = document.getElementById("filter-button");
|
||||
|
||||
let showSpinner = () => {
|
||||
this._toggleListBody(true);
|
||||
emptyBody.classList.add("hidden");
|
||||
};
|
||||
|
||||
let getAllLogins = () => {
|
||||
let logins = [];
|
||||
try {
|
||||
logins = Services.logins.getAllLogins();
|
||||
} catch (e) {
|
||||
// It's likely that the Master Password was not entered; give
|
||||
// a hint to the next person.
|
||||
throw new Error(
|
||||
"Possible Master Password permissions error: " + e.toString()
|
||||
);
|
||||
}
|
||||
|
||||
logins.sort((a, b) => a.origin.localeCompare(b.origin));
|
||||
|
||||
return logins;
|
||||
};
|
||||
|
||||
let hideSpinner = logins => {
|
||||
this._toggleListBody(false);
|
||||
|
||||
if (!logins.length) {
|
||||
contentBody.classList.add("hidden");
|
||||
filterIcon.classList.add("hidden");
|
||||
emptyBody.classList.remove("hidden");
|
||||
} else {
|
||||
contentBody.classList.remove("hidden");
|
||||
emptyBody.classList.add("hidden");
|
||||
}
|
||||
|
||||
return logins;
|
||||
};
|
||||
|
||||
// Return a promise that is resolved after a paint.
|
||||
let waitForPaint = () => {
|
||||
// We're changing 'display'. We need to wait for the new value to take
|
||||
// effect; otherwise, we'll block and never paint a change. Since
|
||||
// requestAnimationFrame callback is generally triggered *before* any
|
||||
// style flush and layout, we wait for two animation frames. This
|
||||
// approach was cribbed from
|
||||
// https://dxr.mozilla.org/mozilla-central/rev/5abe3c4deab94270440422c850bbeaf512b1f38d/browser/base/content/browser-fullScreen.js?offset=0#469.
|
||||
return new Promise(function(resolve, reject) {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// getAllLogins janks the main-thread. We need to paint before that jank;
|
||||
// by throwing the janky load onto the next tick, we paint the spinner; the
|
||||
// spinner is CSS animated off-main-thread.
|
||||
return Promise.resolve()
|
||||
.then(showSpinner)
|
||||
.then(waitForPaint)
|
||||
.then(getAllLogins)
|
||||
.then(hideSpinner);
|
||||
},
|
||||
|
||||
// Reload the logins list, displaying interstitial UI while loading.
|
||||
// Update the stored and displayed list upon completion.
|
||||
_reloadList: function() {
|
||||
this._promiseLogins()
|
||||
.then(logins => {
|
||||
this._logins = logins;
|
||||
this._loadList(logins);
|
||||
})
|
||||
.catch(e => {
|
||||
// There's no way to recover from errors, sadly. Log and make
|
||||
// it obvious that something is up.
|
||||
this._logins = [];
|
||||
debug("Failed to _reloadList!");
|
||||
Cu.reportError(e);
|
||||
});
|
||||
},
|
||||
|
||||
_toggleListBody: function(isLoading) {
|
||||
let contentBody = document.getElementById("content-body");
|
||||
let loadingBody = document.getElementById("logins-list-loading-body");
|
||||
|
||||
if (isLoading) {
|
||||
contentBody.classList.add("hidden");
|
||||
loadingBody.classList.remove("hidden");
|
||||
} else {
|
||||
loadingBody.classList.add("hidden");
|
||||
contentBody.classList.remove("hidden");
|
||||
}
|
||||
},
|
||||
|
||||
init: function() {
|
||||
window.addEventListener("popstate", this);
|
||||
|
||||
Services.obs.addObserver(this, "passwordmgr-storage-changed");
|
||||
document
|
||||
.getElementById("update-btn")
|
||||
.addEventListener("click", this._onSaveEditLogin.bind(this));
|
||||
document
|
||||
.getElementById("password-btn")
|
||||
.addEventListener("click", this._onPasswordBtn.bind(this));
|
||||
|
||||
let filterInput = document.getElementById("filter-input");
|
||||
let filterContainer = document.getElementById("filter-input-container");
|
||||
|
||||
filterInput.addEventListener("input", event => {
|
||||
// Stop any in-progress filter timer
|
||||
if (this._filterTimer) {
|
||||
clearTimeout(this._filterTimer);
|
||||
this._filterTimer = null;
|
||||
}
|
||||
|
||||
// Start a new timer
|
||||
this._filterTimer = setTimeout(() => {
|
||||
this._filter(event);
|
||||
}, FILTER_DELAY);
|
||||
});
|
||||
|
||||
filterInput.addEventListener("blur", event => {
|
||||
filterContainer.setAttribute("hidden", true);
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("filter-button")
|
||||
.addEventListener("click", event => {
|
||||
filterContainer.removeAttribute("hidden");
|
||||
filterInput.focus();
|
||||
});
|
||||
|
||||
document.getElementById("filter-clear").addEventListener("click", event => {
|
||||
// Stop any in-progress filter timer
|
||||
if (this._filterTimer) {
|
||||
clearTimeout(this._filterTimer);
|
||||
this._filterTimer = null;
|
||||
}
|
||||
|
||||
filterInput.blur();
|
||||
filterInput.value = "";
|
||||
this._loadList(this._logins);
|
||||
});
|
||||
|
||||
this._showList();
|
||||
|
||||
this._updatePasswordBtn(true);
|
||||
|
||||
this._reloadList();
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
Services.obs.removeObserver(this, "passwordmgr-storage-changed");
|
||||
window.removeEventListener("popstate", this);
|
||||
},
|
||||
|
||||
_loadList: function(logins) {
|
||||
let list = document.getElementById("logins-list");
|
||||
let newList = list.cloneNode(false);
|
||||
|
||||
logins.forEach(login => {
|
||||
let item = this._createItemForLogin(login);
|
||||
newList.appendChild(item);
|
||||
});
|
||||
|
||||
list.parentNode.replaceChild(newList, list);
|
||||
},
|
||||
|
||||
_showList: function() {
|
||||
let loginsListPage = document.getElementById("logins-list-page");
|
||||
loginsListPage.classList.remove("hidden");
|
||||
|
||||
let editLoginPage = document.getElementById("edit-login-page");
|
||||
editLoginPage.classList.add("hidden");
|
||||
|
||||
// If the Show/Hide password button has been flipped, reset it
|
||||
if (this._isPasswordBtnInHideMode()) {
|
||||
this._updatePasswordBtn(true);
|
||||
}
|
||||
},
|
||||
|
||||
_onPopState: function(event) {
|
||||
// Called when back/forward is used to change the state of the page
|
||||
if (event.state) {
|
||||
this._showEditLoginDialog(event.state.id);
|
||||
} else {
|
||||
this._selectedLogin = null;
|
||||
this._showList();
|
||||
}
|
||||
},
|
||||
_showEditLoginDialog: function(login) {
|
||||
let listPage = document.getElementById("logins-list-page");
|
||||
listPage.classList.add("hidden");
|
||||
|
||||
let editLoginPage = document.getElementById("edit-login-page");
|
||||
editLoginPage.classList.remove("hidden");
|
||||
|
||||
let usernameField = document.getElementById("username");
|
||||
usernameField.value = login.username;
|
||||
let passwordField = document.getElementById("password");
|
||||
passwordField.value = login.password;
|
||||
let domainField = document.getElementById("origin");
|
||||
domainField.value = login.origin;
|
||||
|
||||
let img = document.getElementById("favicon");
|
||||
this._loadFavicon(img, login.origin);
|
||||
|
||||
let headerText = document.getElementById("edit-login-header-text");
|
||||
if (login.origin && login.origin != "") {
|
||||
headerText.textContent = login.origin;
|
||||
} else {
|
||||
headerText.textContent = gStringBundle.GetStringFromName(
|
||||
"editLogin.fallbackTitle"
|
||||
);
|
||||
}
|
||||
|
||||
passwordField.addEventListener("input", event => {
|
||||
let newPassword = passwordField.value;
|
||||
let updateBtn = document.getElementById("update-btn");
|
||||
|
||||
if (newPassword === "") {
|
||||
updateBtn.disabled = true;
|
||||
updateBtn.classList.add("disabled-btn");
|
||||
} else if (newPassword !== "" && updateBtn.disabled === true) {
|
||||
updateBtn.disabled = false;
|
||||
updateBtn.classList.remove("disabled-btn");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_onSaveEditLogin: function() {
|
||||
let newUsername = document.getElementById("username").value;
|
||||
let newPassword = document.getElementById("password").value;
|
||||
let origUsername = this._selectedLogin.username;
|
||||
let origPassword = this._selectedLogin.password;
|
||||
|
||||
try {
|
||||
if (newUsername === origUsername && newPassword === origPassword) {
|
||||
Snackbars.show(
|
||||
gStringBundle.GetStringFromName("editLogin.saved1"),
|
||||
Snackbars.LENGTH_LONG
|
||||
);
|
||||
this._showList();
|
||||
return;
|
||||
}
|
||||
|
||||
let logins = Services.logins.findLogins(
|
||||
this._selectedLogin.origin,
|
||||
this._selectedLogin.formActionOrigin,
|
||||
this._selectedLogin.httpRealm
|
||||
);
|
||||
|
||||
for (let i = 0; i < logins.length; i++) {
|
||||
if (logins[i].username == origUsername) {
|
||||
let propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
|
||||
Ci.nsIWritablePropertyBag
|
||||
);
|
||||
if (newUsername !== origUsername) {
|
||||
propBag.setProperty("username", newUsername);
|
||||
}
|
||||
if (newPassword !== origPassword) {
|
||||
propBag.setProperty("password", newPassword);
|
||||
}
|
||||
// Sync relies on timePasswordChanged to decide whether
|
||||
// or not to sync a login, so touch it.
|
||||
propBag.setProperty("timePasswordChanged", Date.now());
|
||||
Services.logins.modifyLogin(logins[i], propBag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Snackbars.show(
|
||||
gStringBundle.GetStringFromName("editLogin.couldNotSave"),
|
||||
Snackbars.LENGTH_LONG
|
||||
);
|
||||
return;
|
||||
}
|
||||
Snackbars.show(
|
||||
gStringBundle.GetStringFromName("editLogin.saved1"),
|
||||
Snackbars.LENGTH_LONG
|
||||
);
|
||||
this._showList();
|
||||
},
|
||||
|
||||
_onPasswordBtn: function() {
|
||||
this._updatePasswordBtn(this._isPasswordBtnInHideMode());
|
||||
},
|
||||
|
||||
_updatePasswordBtn: function(aShouldShow) {
|
||||
let passwordField = document.getElementById("password");
|
||||
let button = document.getElementById("password-btn");
|
||||
let show = gStringBundle.GetStringFromName("password-btn.show");
|
||||
let hide = gStringBundle.GetStringFromName("password-btn.hide");
|
||||
if (aShouldShow) {
|
||||
passwordField.type = "password";
|
||||
button.textContent = show;
|
||||
button.classList.remove("password-btn-hide");
|
||||
} else {
|
||||
passwordField.type = "text";
|
||||
button.textContent = hide;
|
||||
button.classList.add("password-btn-hide");
|
||||
}
|
||||
},
|
||||
|
||||
_isPasswordBtnInHideMode: function() {
|
||||
let button = document.getElementById("password-btn");
|
||||
return button.classList.contains("password-btn-hide");
|
||||
},
|
||||
|
||||
_showPassword: function(password) {
|
||||
let passwordPrompt = new Prompt({
|
||||
window: window,
|
||||
message: password,
|
||||
buttons: [
|
||||
gStringBundle.GetStringFromName("loginsDialog.copy"),
|
||||
gStringBundle.GetStringFromName("loginsDialog.cancel"),
|
||||
],
|
||||
}).show(data => {
|
||||
switch (data.button) {
|
||||
case 0:
|
||||
// Corresponds to "Copy password" button.
|
||||
copyStringShowSnackbar(
|
||||
password,
|
||||
gStringBundle.GetStringFromName("loginsDetails.passwordCopied")
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_onLoginClick: function(event) {
|
||||
let loginItem = event.currentTarget;
|
||||
let login = loginItem.login;
|
||||
if (!login) {
|
||||
debug("No login!");
|
||||
return;
|
||||
}
|
||||
|
||||
let prompt = new Prompt({
|
||||
window: window,
|
||||
});
|
||||
let menuItems = [
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.showPassword") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.copyPassword") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.copyUsername") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.editLogin") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.delete") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.deleteAll") },
|
||||
];
|
||||
|
||||
prompt.setSingleChoiceItems(menuItems);
|
||||
prompt.show(data => {
|
||||
// Switch on indices of buttons, as they were added when creating login item.
|
||||
switch (data.button) {
|
||||
case 0:
|
||||
this._showPassword(login.password);
|
||||
break;
|
||||
case 1:
|
||||
copyStringShowSnackbar(
|
||||
login.password,
|
||||
gStringBundle.GetStringFromName("loginsDetails.passwordCopied")
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
copyStringShowSnackbar(
|
||||
login.username,
|
||||
gStringBundle.GetStringFromName("loginsDetails.usernameCopied")
|
||||
);
|
||||
break;
|
||||
case 3:
|
||||
this._selectedLogin = login;
|
||||
this._showEditLoginDialog(login);
|
||||
history.pushState({ id: login.guid }, document.title);
|
||||
break;
|
||||
case 4:
|
||||
Accounts.getFirefoxAccount().then(user => {
|
||||
const promptMessage = user
|
||||
? gStringBundle.GetStringFromName(
|
||||
"loginsDialog.confirmDeleteForFxaUser"
|
||||
)
|
||||
: gStringBundle.GetStringFromName("loginsDialog.confirmDelete");
|
||||
const confirmationMessage = gStringBundle.GetStringFromName(
|
||||
"loginsDetails.deleted"
|
||||
);
|
||||
|
||||
this._showConfirmationPrompt(
|
||||
promptMessage,
|
||||
confirmationMessage,
|
||||
() => Services.logins.removeLogin(login)
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 5:
|
||||
Accounts.getFirefoxAccount().then(user => {
|
||||
const promptMessage = user
|
||||
? gStringBundle.GetStringFromName(
|
||||
"loginsDialog.confirmDeleteAllForFxaUser"
|
||||
)
|
||||
: gStringBundle.GetStringFromName(
|
||||
"loginsDialog.confirmDeleteAll"
|
||||
);
|
||||
const confirmationMessage = gStringBundle.GetStringFromName(
|
||||
"loginsDetails.deletedAll"
|
||||
);
|
||||
|
||||
this._showConfirmationPrompt(
|
||||
promptMessage,
|
||||
confirmationMessage,
|
||||
() => Services.logins.removeAllLogins()
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_showConfirmationPrompt: function(
|
||||
promptMessage,
|
||||
confirmationMessage,
|
||||
actionToPerform
|
||||
) {
|
||||
new Prompt({
|
||||
window: window,
|
||||
message: promptMessage,
|
||||
buttons: [
|
||||
// Use default, generic values
|
||||
gStringBundle.GetStringFromName("loginsDialog.confirm"),
|
||||
gStringBundle.GetStringFromName("loginsDialog.cancel"),
|
||||
],
|
||||
}).show(data => {
|
||||
switch (data.button) {
|
||||
case 0:
|
||||
// Corresponds to "confirm" button.
|
||||
|
||||
actionToPerform();
|
||||
|
||||
Snackbars.show(confirmationMessage, Snackbars.LENGTH_LONG);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_loadFavicon: function(aImg, aOrigin) {
|
||||
// Load favicon from cache.
|
||||
EventDispatcher.instance
|
||||
.sendRequestForResult({
|
||||
type: "Favicon:Request",
|
||||
url: aOrigin,
|
||||
skipNetwork: true,
|
||||
})
|
||||
.then(
|
||||
function(faviconUrl) {
|
||||
aImg.style.backgroundImage = "url('" + faviconUrl + "')";
|
||||
aImg.style.visibility = "visible";
|
||||
},
|
||||
function(data) {
|
||||
debug("Favicon cache failure : " + data);
|
||||
aImg.style.visibility = "visible";
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
_createItemForLogin: function(login) {
|
||||
let loginItem = document.createElement("div");
|
||||
|
||||
loginItem.setAttribute("loginID", login.guid);
|
||||
loginItem.className = "login-item list-item";
|
||||
|
||||
loginItem.addEventListener("click", this, true);
|
||||
loginItem.addEventListener("contextmenu", this, true);
|
||||
|
||||
// Create item icon.
|
||||
let img = document.createElement("div");
|
||||
img.className = "icon";
|
||||
|
||||
this._loadFavicon(img, login.origin);
|
||||
loginItem.appendChild(img);
|
||||
|
||||
// Create item details.
|
||||
let inner = document.createElement("div");
|
||||
inner.className = "inner";
|
||||
|
||||
let details = document.createElement("div");
|
||||
details.className = "details";
|
||||
inner.appendChild(details);
|
||||
|
||||
let titlePart = document.createElement("div");
|
||||
titlePart.className = "origin";
|
||||
titlePart.textContent = login.origin;
|
||||
details.appendChild(titlePart);
|
||||
|
||||
let versionPart = document.createElement("div");
|
||||
versionPart.textContent = login.httpRealm;
|
||||
versionPart.className = "realm";
|
||||
details.appendChild(versionPart);
|
||||
|
||||
let descPart = document.createElement("div");
|
||||
descPart.textContent = login.username;
|
||||
descPart.className = "username";
|
||||
inner.appendChild(descPart);
|
||||
|
||||
loginItem.appendChild(inner);
|
||||
loginItem.login = login;
|
||||
return loginItem;
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
switch (event.type) {
|
||||
case "popstate": {
|
||||
this._onPopState(event);
|
||||
break;
|
||||
}
|
||||
case "contextmenu":
|
||||
case "click": {
|
||||
this._onLoginClick(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "passwordmgr-storage-changed": {
|
||||
this._reloadList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_filter: function(event) {
|
||||
let value = event.target.value.toLowerCase();
|
||||
let logins = this._logins.filter(login => {
|
||||
if (login.origin.toLowerCase().includes(value)) {
|
||||
return true;
|
||||
}
|
||||
if (login.username && login.username.toLowerCase().includes(value)) {
|
||||
return true;
|
||||
}
|
||||
if (login.httpRealm && login.httpRealm.toLowerCase().includes(value)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
this._loadList(logins);
|
||||
},
|
||||
};
|
||||
|
||||
window.addEventListener("load", Logins.init.bind(Logins));
|
||||
window.addEventListener("unload", Logins.uninit.bind(Logins));
|
@ -1,90 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
|
||||
%globalDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % aboutDTD SYSTEM "chrome://browser/locale/aboutLogins.dtd" >
|
||||
%aboutDTD;
|
||||
]>
|
||||
<!-- 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 xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&aboutLogins.title;</title>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
||||
<link rel="stylesheet" href="chrome://browser/skin/spinner.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutLogins.css" type="text/css"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/aboutLogins.js"></script>
|
||||
</head>
|
||||
<body dir="&locale.dir;">
|
||||
|
||||
<div id="logins-list-page">
|
||||
<div id="logins-header" class="header">
|
||||
<div>&aboutLogins.title;</div>
|
||||
<ul class="toolbar-buttons">
|
||||
<li id="filter-button"></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="content-body">
|
||||
<div id="logins-list" class="list"/>
|
||||
<div id="filter-input-container" hidden="true">
|
||||
<input id="filter-input" type="search"/>
|
||||
<div id="filter-clear"/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="logins-list-loading-body" class="hidden">
|
||||
<div id="loading-img-container">
|
||||
|
||||
<div id="spinner" class="mui-refresh-main">
|
||||
<div class="mui-refresh-wrapper">
|
||||
<div class="mui-spinner-wrapper">
|
||||
<div class="mui-spinner-main">
|
||||
<div class="mui-spinner-left">
|
||||
<div class="mui-half-circle-left" />
|
||||
</div>
|
||||
<div class="mui-spinner-right">
|
||||
<div class="mui-half-circle-right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="empty-body" class="hidden">
|
||||
<div id="empty-obj-text-container">
|
||||
<object type="image/svg+xml" id="empty-icon" data="chrome://browser/skin/images/icon_key_emptypage.svg"/>
|
||||
<div class="empty-text">&aboutLogins.emptyLoginText;</div>
|
||||
<div class="empty-hint">&aboutLogins.emptyLoginHint;</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="edit-login-page" class="hidden">
|
||||
<div id="edit-login-header" class="header">
|
||||
<div id="edit-login-header-text"/>
|
||||
</div>
|
||||
<div class="edit-login-div">
|
||||
<div id="favicon" class="edit-login-icon"/>
|
||||
<input type="text" name="origin" id="origin" class="edit-login-input" disabled="disabled"/>
|
||||
</div>
|
||||
<div class="edit-login-div">
|
||||
<input type="text" name="username" id="username" class="edit-login-input" autocomplete="off"/>
|
||||
</div>
|
||||
<div class="edit-login-div">
|
||||
<input type="password" id="password" name="password" value="password" class="edit-login-input" />
|
||||
<button id="password-btn"></button>
|
||||
</div>
|
||||
<div class="edit-login-div">
|
||||
<button id="update-btn" class="update-button">&aboutLogins.update;</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,36 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { PrivateBrowsingUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(
|
||||
window,
|
||||
"gChromeWin",
|
||||
() => window.docShell.rootTreeItem.domWindow
|
||||
);
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
let BrowserApp = window.gChromeWin.BrowserApp;
|
||||
|
||||
if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) {
|
||||
document.body.setAttribute("class", "normal");
|
||||
document
|
||||
.getElementById("newPrivateTabLink")
|
||||
.addEventListener("click", function() {
|
||||
BrowserApp.addTab("about:privatebrowsing", {
|
||||
selected: true,
|
||||
parentId: BrowserApp.selectedTab.id,
|
||||
isPrivate: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
# 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/.
|
||||
-->
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % privatebrowsingpageDTD SYSTEM "chrome://browser/locale/aboutPrivateBrowsing.dtd">
|
||||
%privatebrowsingpageDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&privatebrowsingpage.title;</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1; user-scalable=no"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutPrivateBrowsing.css" type="text/css" media="all"/>
|
||||
<link rel="icon" type="image/png" href="chrome://branding/content/favicon32.png" />
|
||||
<script type="application/javascript" src="chrome://browser/content/aboutPrivateBrowsing.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="private" dir="&locale.dir;">
|
||||
<img class="showPrivate masq" src="chrome://browser/skin/images/privatebrowsing-mask-and-shield.svg" />
|
||||
<img class="showNormal masq" src="chrome://browser/skin/images/privatebrowsing-mask.png" />
|
||||
|
||||
<h1 class="showPrivate">&privatebrowsingpage.title;<br />&privatebrowsingpage.title.private;</h1>
|
||||
<h1 class="showNormal">&privatebrowsingpage.title.normal1;</h1>
|
||||
|
||||
<div class="contentSection">
|
||||
<p class="showPrivate">&privatebrowsingpage.description.trackingProtection;<br /><br />&privatebrowsingpage.description.privateDetails;</p>
|
||||
<p class="showNormal">&privatebrowsingpage.description.normal2;</p>
|
||||
|
||||
<p class="showPrivate"><a href="https://support.mozilla.org/kb/private-browsing-firefox-android">&privatebrowsingpage.link.private;</a></p>
|
||||
<p class="showNormal"><a href="#" id="newPrivateTabLink">&privatebrowsingpage.link.normal;</a></p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,95 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
]>
|
||||
|
||||
<!-- 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 xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<title data-l10n-id="rights-title"></title>
|
||||
<link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css" type="text/css"/>
|
||||
<link rel="localization" href="branding/brand.ftl"/>
|
||||
<link rel="localization" href="toolkit/about/aboutRights.ftl"/>
|
||||
</head>
|
||||
|
||||
<body id="your-rights">
|
||||
<div class="container">
|
||||
<h1 data-l10n-id="rights-title"></h1>
|
||||
|
||||
<p data-l10n-id="rights-intro"></p>
|
||||
|
||||
<ul>
|
||||
<li data-l10n-id="rights-intro-point-1"><a href="http://www.mozilla.org/MPL/" data-l10n-name="mozilla-public-license-link"></a></li>
|
||||
<!-- Point 2 discusses Mozilla trademarks, and isn't needed when the build is unbranded.
|
||||
- Point 4 discusses privacy policy, unbranded builds get a placeholder (for the vendor to replace)
|
||||
- Point 5 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace) -->
|
||||
<li data-l10n-id="rights-intro-point-2"><a href="http://www.mozilla.org/foundation/trademarks/policy.html" data-l10n-name="mozilla-trademarks-link"></a></li>
|
||||
<li data-l10n-id="rights-intro-point-3"></li>
|
||||
<li data-l10n-id="rights-intro-point-4"><a href="https://www.mozilla.org/legal/privacy/firefox.html" data-l10n-name="mozilla-privacy-policy-link"></a></li>
|
||||
<li data-l10n-id="rights-intro-point-5"><a href="about:rights#webservices" onclick="showServices();" data-l10n-name="mozilla-service-terms-link"></a></li>
|
||||
<li data-l10n-id="rights-intro-point-6"></li>
|
||||
</ul>
|
||||
|
||||
<div id="webservices-container">
|
||||
<a name="webservices"/>
|
||||
<h3 data-l10n-id="rights-webservices-header"></h3>
|
||||
|
||||
<p data-l10n-id="rights-webservices"><a href="about:rights#disabling-webservices" onclick="showDisablingServices();" id="showDisablingWebServices" data-l10n-name="mozilla-disable-service-link"></a></p>
|
||||
|
||||
<div id="disabling-webservices-container" style="margin-left:40px;">
|
||||
<a name="disabling-webservices"/>
|
||||
<!-- Safe Browsing cannot be disabled in Firefox Mobile; these instructions show how to do it on desktop
|
||||
<p><strong>&rights.safebrowsing-a;</strong>&rights.safebrowsing-b;</p>
|
||||
<ul>
|
||||
<li>&rights.safebrowsing-term1;</li>
|
||||
<li>&rights.safebrowsing-term2;</li>
|
||||
<li>&rights2.safebrowsing-term3;</li>
|
||||
<li>&rights.safebrowsing-term4;</li>
|
||||
</ul>
|
||||
-->
|
||||
|
||||
<p data-l10n-id="rights-locationawarebrowsing"></p>
|
||||
<ul>
|
||||
<li data-l10n-id="rights-locationawarebrowsing-term-1"></li>
|
||||
<li data-l10n-id="rights-locationawarebrowsing-term-2"></li>
|
||||
<li data-l10n-id="rights-locationawarebrowsing-term-3"></li>
|
||||
<li data-l10n-id="rights-locationawarebrowsing-term-4"></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ol>
|
||||
<!-- Terms only apply to official builds, unbranded builds get a placeholder. -->
|
||||
<li data-l10n-id="rights-webservices-term-1"></li>
|
||||
<li data-l10n-id="rights-webservices-term-2"></li>
|
||||
<li data-l10n-id="rights-webservices-term-3"></li>
|
||||
<li data-l10n-id="rights-webservices-term-4"></li>
|
||||
<li data-l10n-id="rights-webservices-term-5"></li>
|
||||
<li data-l10n-id="rights-webservices-term-6"></li>
|
||||
<li data-l10n-id="rights-webservices-term-7"></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
var servicesDiv = document.getElementById("webservices-container");
|
||||
servicesDiv.style.display = "none";
|
||||
|
||||
function showServices() {
|
||||
servicesDiv.style.display = "";
|
||||
}
|
||||
|
||||
var disablingServicesDiv = document.getElementById("disabling-webservices-container");
|
||||
disablingServicesDiv.style.display = "none";
|
||||
|
||||
function showDisablingServices() {
|
||||
disablingServicesDiv.style.display = "";
|
||||
}
|
||||
]]></script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,211 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % blockedSiteDTD SYSTEM "chrome://browser/locale/phishing.dtd">
|
||||
%blockedSiteDTD;
|
||||
]>
|
||||
|
||||
<!-- 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 xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=false" />
|
||||
<link rel="stylesheet" href="chrome://browser/skin/netError.css" media="all" />
|
||||
<link rel="icon" type="image/png" id="favicon" sizes="64x64" href="chrome://browser/skin/images/blocked-warning.png"/>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
// Error url MUST be formatted like this:
|
||||
// about:blocked?e=error_code&u=url(&o=1)?
|
||||
// (o=1 when user overrides are allowed)
|
||||
|
||||
// Note that this file uses document.documentURI to get
|
||||
// the URL (with the format from above). This is because
|
||||
// document.location.href gets the current URI off the docshell,
|
||||
// which is the URL displayed in the location bar, i.e.
|
||||
// the URI that the user attempted to load.
|
||||
|
||||
function getErrorCode() {
|
||||
var url = document.documentURI;
|
||||
var error = url.search(/e\=/);
|
||||
var duffUrl = url.search(/\&u\=/);
|
||||
return decodeURIComponent(url.slice(error + 2, duffUrl));
|
||||
}
|
||||
|
||||
function getURL() {
|
||||
var url = document.documentURI;
|
||||
var match = url.match(/&u=([^&]+)&/);
|
||||
|
||||
// match == null if not found; if so, return an empty string
|
||||
// instead of what would turn out to be portions of the URI
|
||||
if (!match)
|
||||
return "";
|
||||
|
||||
url = decodeURIComponent(match[1]);
|
||||
|
||||
// If this is a view-source page, then get then real URI of the page
|
||||
if (/^view-source\:/.test(url))
|
||||
url = url.slice(12);
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this warning page should be overridable or whether
|
||||
* the "ignore warning" button should be hidden.
|
||||
*/
|
||||
function getOverride() {
|
||||
var url = document.documentURI;
|
||||
var match = url.match(/&o=1&/);
|
||||
return !!match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to get the hostname via document.location. Fail back
|
||||
* to getURL so that we always return something meaningful.
|
||||
*/
|
||||
function getHostString() {
|
||||
try {
|
||||
return document.location.hostname;
|
||||
} catch (e) {
|
||||
return getURL();
|
||||
}
|
||||
}
|
||||
|
||||
function initPage() {
|
||||
var error = "";
|
||||
switch (getErrorCode()) {
|
||||
case "malwareBlocked" :
|
||||
error = "malware";
|
||||
break;
|
||||
case "deceptiveBlocked" :
|
||||
error = "phishing";
|
||||
break;
|
||||
case "unwantedBlocked" :
|
||||
error = "unwanted";
|
||||
break;
|
||||
case "harmfulBlocked" :
|
||||
error = "harmful";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
var el;
|
||||
|
||||
if (error !== "malware") {
|
||||
el = document.getElementById("errorTitleText_malware");
|
||||
el.remove();
|
||||
el = document.getElementById("errorShortDescText_malware");
|
||||
el.remove();
|
||||
el = document.getElementById("errorLongDescText_malware");
|
||||
el.remove();
|
||||
}
|
||||
|
||||
if (error !== "phishing") {
|
||||
el = document.getElementById("errorTitleText_phishing");
|
||||
el.remove();
|
||||
el = document.getElementById("errorShortDescText_phishing");
|
||||
el.remove();
|
||||
el = document.getElementById("errorLongDescText_phishing");
|
||||
el.remove();
|
||||
}
|
||||
|
||||
if (error !== "unwanted") {
|
||||
el = document.getElementById("errorTitleText_unwanted");
|
||||
el.remove();
|
||||
el = document.getElementById("errorShortDescText_unwanted");
|
||||
el.remove();
|
||||
el = document.getElementById("errorLongDescText_unwanted");
|
||||
el.remove();
|
||||
}
|
||||
|
||||
if (error !== "harmful") {
|
||||
el = document.getElementById("errorTitleText_harmful");
|
||||
el.remove();
|
||||
el = document.getElementById("errorShortDescText_harmful");
|
||||
el.remove();
|
||||
}
|
||||
|
||||
if (!getOverride()) {
|
||||
var btn = document.getElementById("ignoreWarningButton");
|
||||
if (btn) {
|
||||
btn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Set sitename
|
||||
let siteElem = document.getElementById(error + "_sitename");
|
||||
if (siteElem) {
|
||||
siteElem.textContent = getHostString();
|
||||
}
|
||||
|
||||
document.title = document.getElementById("errorTitleText_" + error)
|
||||
.innerHTML;
|
||||
|
||||
// Inform the test harness that we're done loading the page
|
||||
var event = new CustomEvent("AboutBlockedLoaded", { bubbles: true });
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
]]></script>
|
||||
</head>
|
||||
|
||||
<body id="errorPage" class="blockedsite" dir="&locale.dir;">
|
||||
|
||||
<div id="errorPageContainer">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div id="errorTitle">
|
||||
<h1 id="errorTitleText_phishing" class="errorTitleText">&safeb.blocked.phishingPage.title3;</h1>
|
||||
<h1 id="errorTitleText_malware" class="errorTitleText">&safeb.blocked.malwarePage.title;</h1>
|
||||
<h1 id="errorTitleText_unwanted" class="errorTitleText">&safeb.blocked.unwantedPage.title;</h1>
|
||||
<h1 id="errorTitleText_harmful" class="errorTitleText">&safeb.blocked.harmfulPage.title;</h1>
|
||||
</div>
|
||||
|
||||
<div id="errorLongContent">
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc3;</p>
|
||||
<p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
|
||||
<p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p>
|
||||
<p id="errorShortDescText_harmful">&safeb.blocked.harmfulPage.shortDesc;</p>
|
||||
</div>
|
||||
|
||||
<!-- Long Description -->
|
||||
<div id="errorLongDesc">
|
||||
<p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc3;</p>
|
||||
<p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
|
||||
<p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p>
|
||||
</div>
|
||||
|
||||
<!-- Advisory -->
|
||||
<div id="advisoryDesc">
|
||||
<p id="advisoryDescText">&safeb.palm.advisory.desc;</p>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div id="buttons">
|
||||
<!-- Commands handled in browser.js -->
|
||||
<button id="getMeOutButton">&safeb.palm.accept.label;</button>
|
||||
<button id="reportButton">&safeb.palm.reportPage.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ignoreWarning">
|
||||
<button id="ignoreWarningButton">&safeb.palm.decline.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
- Note: It is important to run the script this way, instead of using
|
||||
- an onload handler. This is because error pages are loaded as
|
||||
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
|
||||
-->
|
||||
<script type="application/javascript">initPage();</script>
|
||||
</body>
|
||||
</html>
|
@ -1,4 +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/. */
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,17 +0,0 @@
|
||||
<?xml version="1.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/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
|
||||
|
||||
<window id="main-window"
|
||||
onload="BrowserApp.startup();"
|
||||
windowtype="navigator:browser"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/browser.js"/>
|
||||
|
||||
<deck id="browsers" flex="1"/>
|
||||
|
||||
</window>
|
@ -1,637 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
var { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AboutReader",
|
||||
"resource://gre/modules/AboutReader.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"ReaderMode",
|
||||
"resource://gre/modules/ReaderMode.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Readerable",
|
||||
"resource://gre/modules/Readerable.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gPipNSSBundle", function() {
|
||||
return Services.strings.createBundle(
|
||||
"chrome://pipnss/locale/pipnss.properties"
|
||||
);
|
||||
});
|
||||
XPCOMUtils.defineLazyGetter(this, "gNSSErrorsBundle", function() {
|
||||
return Services.strings.createBundle(
|
||||
"chrome://pipnss/locale/nsserrors.properties"
|
||||
);
|
||||
});
|
||||
|
||||
var dump = ChromeUtils.import(
|
||||
"resource://gre/modules/AndroidLog.jsm",
|
||||
{}
|
||||
).AndroidLog.d.bind(null, "Content");
|
||||
|
||||
var global = this;
|
||||
|
||||
var AboutBlockedSiteListener = {
|
||||
init(chromeGlobal) {
|
||||
addEventListener("AboutBlockedLoaded", this, false, true);
|
||||
},
|
||||
|
||||
get isBlockedSite() {
|
||||
return content.document.documentURI.startsWith("about:blocked");
|
||||
},
|
||||
|
||||
handleEvent(aEvent) {
|
||||
if (!this.isBlockedSite) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aEvent.type != "AboutBlockedLoaded") {
|
||||
return;
|
||||
}
|
||||
|
||||
let provider = "";
|
||||
if (docShell.failedChannel) {
|
||||
let classifiedChannel = docShell.failedChannel.QueryInterface(
|
||||
Ci.nsIClassifiedChannel
|
||||
);
|
||||
if (classifiedChannel) {
|
||||
provider = classifiedChannel.matchedProvider;
|
||||
}
|
||||
}
|
||||
|
||||
let advisoryUrl = Services.prefs.getCharPref(
|
||||
"browser.safebrowsing.provider." + provider + ".advisoryURL",
|
||||
""
|
||||
);
|
||||
if (!advisoryUrl) {
|
||||
let el = content.document.getElementById("advisoryDesc");
|
||||
el.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
let advisoryLinkText = Services.prefs.getCharPref(
|
||||
"browser.safebrowsing.provider." + provider + ".advisoryName",
|
||||
""
|
||||
);
|
||||
if (!advisoryLinkText) {
|
||||
let el = content.document.getElementById("advisoryDesc");
|
||||
el.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
let anchorEl = content.document.getElementById("advisory_provider");
|
||||
anchorEl.setAttribute("href", advisoryUrl);
|
||||
anchorEl.textContent = advisoryLinkText;
|
||||
},
|
||||
};
|
||||
AboutBlockedSiteListener.init();
|
||||
|
||||
/* The following code, in particular AboutCertErrorListener and
|
||||
* AboutNetErrorListener, is mostly copied from content browser.js and content.js.
|
||||
* Certificate error handling should be unified to remove this duplicated code.
|
||||
*/
|
||||
|
||||
const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
|
||||
const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE;
|
||||
|
||||
const SEC_ERROR_EXPIRED_CERTIFICATE = SEC_ERROR_BASE + 11;
|
||||
const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13;
|
||||
const SEC_ERROR_UNTRUSTED_ISSUER = SEC_ERROR_BASE + 20;
|
||||
const SEC_ERROR_UNTRUSTED_CERT = SEC_ERROR_BASE + 21;
|
||||
const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30;
|
||||
const SEC_ERROR_CA_CERT_INVALID = SEC_ERROR_BASE + 36;
|
||||
const SEC_ERROR_OCSP_FUTURE_RESPONSE = SEC_ERROR_BASE + 131;
|
||||
const SEC_ERROR_OCSP_OLD_RESPONSE = SEC_ERROR_BASE + 132;
|
||||
const SEC_ERROR_REUSED_ISSUER_AND_SERIAL = SEC_ERROR_BASE + 138;
|
||||
const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = SEC_ERROR_BASE + 176;
|
||||
const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE =
|
||||
MOZILLA_PKIX_ERROR_BASE + 5;
|
||||
const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE =
|
||||
MOZILLA_PKIX_ERROR_BASE + 6;
|
||||
const MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED =
|
||||
MOZILLA_PKIX_ERROR_BASE + 13;
|
||||
|
||||
const SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
|
||||
const SSL_ERROR_SSL_DISABLED = SSL_ERROR_BASE + 20;
|
||||
const SSL_ERROR_SSL2_DISABLED = SSL_ERROR_BASE + 14;
|
||||
|
||||
var AboutNetErrorListener = {
|
||||
init(chromeGlobal) {
|
||||
addEventListener("AboutNetErrorLoad", this, false, true);
|
||||
},
|
||||
|
||||
get isNetErrorSite() {
|
||||
return content.document.documentURI.startsWith("about:neterror");
|
||||
},
|
||||
|
||||
_getErrorMessageFromCode(securityInfo, doc) {
|
||||
let uri = Services.io.newURI(doc.location);
|
||||
let hostString = uri.host;
|
||||
if (uri.port != 443 && uri.port != -1) {
|
||||
hostString += ":" + uri.port;
|
||||
}
|
||||
|
||||
let id_str = "";
|
||||
switch (securityInfo.errorCode) {
|
||||
case SSL_ERROR_SSL_DISABLED:
|
||||
id_str = "PSMERR_SSL_Disabled";
|
||||
break;
|
||||
case SSL_ERROR_SSL2_DISABLED:
|
||||
id_str = "PSMERR_SSL2_Disabled";
|
||||
break;
|
||||
case SEC_ERROR_REUSED_ISSUER_AND_SERIAL:
|
||||
id_str = "PSMERR_HostReusedIssuerSerial";
|
||||
break;
|
||||
}
|
||||
let nss_error_id_str = securityInfo.errorCodeString;
|
||||
let msg2 = "";
|
||||
if (id_str) {
|
||||
msg2 = gPipNSSBundle.GetStringFromName(id_str) + "\n";
|
||||
} else if (nss_error_id_str) {
|
||||
msg2 = gNSSErrorsBundle.GetStringFromName(nss_error_id_str) + "\n";
|
||||
}
|
||||
|
||||
if (!msg2) {
|
||||
// We couldn't get an error message. Use the error string.
|
||||
// Note that this is different from before where we used PR_ErrorToString.
|
||||
msg2 = nss_error_id_str;
|
||||
}
|
||||
let msg = gPipNSSBundle.formatStringFromName("SSLConnectionErrorPrefix2", [
|
||||
hostString,
|
||||
msg2,
|
||||
]);
|
||||
|
||||
if (nss_error_id_str) {
|
||||
msg += gPipNSSBundle.formatStringFromName("certErrorCodePrefix3", [
|
||||
nss_error_id_str,
|
||||
]);
|
||||
}
|
||||
return msg;
|
||||
},
|
||||
|
||||
handleEvent(aEvent) {
|
||||
if (!this.isNetErrorSite) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aEvent.type != "AboutNetErrorLoad") {
|
||||
return;
|
||||
}
|
||||
|
||||
let { securityInfo } = docShell.failedChannel;
|
||||
// We don't have a securityInfo when this is for example a DNS error.
|
||||
if (securityInfo) {
|
||||
securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
|
||||
let msg = this._getErrorMessageFromCode(
|
||||
securityInfo,
|
||||
aEvent.originalTarget.ownerGlobal
|
||||
);
|
||||
let id = content.document.getElementById("errorShortDescText");
|
||||
id.textContent = msg;
|
||||
}
|
||||
},
|
||||
};
|
||||
AboutNetErrorListener.init();
|
||||
|
||||
var AboutCertErrorListener = {
|
||||
init(chromeGlobal) {
|
||||
addEventListener("AboutCertErrorLoad", this, false, true);
|
||||
},
|
||||
|
||||
get isCertErrorSite() {
|
||||
return content.document.documentURI.startsWith("about:certerror");
|
||||
},
|
||||
|
||||
_setTechDetailsMsgPart1(hostString, securityInfo, technicalInfo, doc) {
|
||||
let msg = gPipNSSBundle.formatStringFromName("certErrorIntro", [
|
||||
hostString,
|
||||
]);
|
||||
msg += "\n\n";
|
||||
|
||||
if (securityInfo.isUntrusted && !securityInfo.serverCert.isSelfSigned) {
|
||||
switch (securityInfo.errorCode) {
|
||||
case SEC_ERROR_UNKNOWN_ISSUER:
|
||||
msg +=
|
||||
gPipNSSBundle.GetStringFromName("certErrorTrust_UnknownIssuer") +
|
||||
"\n";
|
||||
msg +=
|
||||
gPipNSSBundle.GetStringFromName("certErrorTrust_UnknownIssuer2") +
|
||||
"\n";
|
||||
msg +=
|
||||
gPipNSSBundle.GetStringFromName("certErrorTrust_UnknownIssuer3") +
|
||||
"\n";
|
||||
break;
|
||||
case SEC_ERROR_CA_CERT_INVALID:
|
||||
msg +=
|
||||
gPipNSSBundle.GetStringFromName("certErrorTrust_CaInvalid") + "\n";
|
||||
break;
|
||||
case SEC_ERROR_UNTRUSTED_ISSUER:
|
||||
msg +=
|
||||
gPipNSSBundle.GetStringFromName("certErrorTrust_Issuer") + "\n";
|
||||
break;
|
||||
case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
|
||||
msg +=
|
||||
gPipNSSBundle.GetStringFromName(
|
||||
"certErrorTrust_SignatureAlgorithmDisabled"
|
||||
) + "\n";
|
||||
break;
|
||||
case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
|
||||
msg +=
|
||||
gPipNSSBundle.GetStringFromName("certErrorTrust_ExpiredIssuer") +
|
||||
"\n";
|
||||
break;
|
||||
// This error code currently only exists for the Symantec distrust, we may need to adjust
|
||||
// it to fit other distrusts later.
|
||||
case MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED:
|
||||
msg +=
|
||||
gPipNSSBundle.formatStringFromName("certErrorTrust_Symantec", [
|
||||
hostString,
|
||||
]) + "\n";
|
||||
break;
|
||||
case SEC_ERROR_UNTRUSTED_CERT:
|
||||
default:
|
||||
msg +=
|
||||
gPipNSSBundle.GetStringFromName("certErrorTrust_Untrusted") + "\n";
|
||||
}
|
||||
}
|
||||
if (securityInfo.isUntrusted && securityInfo.serverCert.isSelfSigned) {
|
||||
msg +=
|
||||
gPipNSSBundle.GetStringFromName("certErrorTrust_SelfSigned") + "\n";
|
||||
}
|
||||
|
||||
technicalInfo.appendChild(doc.createTextNode(msg));
|
||||
},
|
||||
|
||||
_setTechDetails(securityInfo, location) {
|
||||
if (!securityInfo || !location) {
|
||||
return;
|
||||
}
|
||||
let validity = securityInfo.serverCert.validity;
|
||||
|
||||
let doc = content.document;
|
||||
// CSS class and error code are set from nsDocShell.
|
||||
let searchParams = new URLSearchParams(doc.documentURI.split("?")[1]);
|
||||
let cssClass = searchParams.get("s");
|
||||
let error = searchParams.get("e");
|
||||
let technicalInfo = doc.getElementById("technicalContentText");
|
||||
|
||||
let uri = Services.io.newURI(location);
|
||||
let hostString = uri.host;
|
||||
if (uri.port != 443 && uri.port != -1) {
|
||||
hostString += ":" + uri.port;
|
||||
}
|
||||
|
||||
// This error code currently only exists for the Symantec distrust
|
||||
// in Firefox 63, so we add copy explaining that to the user.
|
||||
// In case of future distrusts of that scale we might need to add
|
||||
// additional parameters that allow us to identify the affected party
|
||||
// without replicating the complex logic from certverifier code.
|
||||
if (
|
||||
securityInfo.errorCode ==
|
||||
MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED
|
||||
) {
|
||||
let introContent = doc.getElementById("introContent");
|
||||
let description = doc.createElement("p");
|
||||
description.textContent = gPipNSSBundle.formatStringFromName(
|
||||
"certErrorSymantecDistrustDescription",
|
||||
[hostString]
|
||||
);
|
||||
introContent.append(description);
|
||||
|
||||
// The regular "what should I do" message does not make sense in this case.
|
||||
doc.getElementById(
|
||||
"whatShouldIDoContentText"
|
||||
).textContent = gPipNSSBundle.GetStringFromName(
|
||||
"certErrorSymantecDistrustAdministrator"
|
||||
);
|
||||
}
|
||||
|
||||
this._setTechDetailsMsgPart1(hostString, securityInfo, technicalInfo, doc);
|
||||
|
||||
if (securityInfo.isDomainMismatch) {
|
||||
let subjectAltNamesList = securityInfo.serverCert.subjectAltNames;
|
||||
let subjectAltNames = subjectAltNamesList.split(",");
|
||||
let numSubjectAltNames = subjectAltNames.length;
|
||||
let msgPrefix = "";
|
||||
if (numSubjectAltNames != 0) {
|
||||
if (numSubjectAltNames == 1) {
|
||||
msgPrefix = gPipNSSBundle.GetStringFromName(
|
||||
"certErrorMismatchSinglePrefix"
|
||||
);
|
||||
|
||||
// Let's check if we want to make this a link.
|
||||
let okHost = subjectAltNamesList;
|
||||
let href = "";
|
||||
let thisHost = doc.location.hostname;
|
||||
let proto = doc.location.protocol + "//";
|
||||
// If okHost is a wildcard domain ("*.example.com") let's
|
||||
// use "www" instead. "*.example.com" isn't going to
|
||||
// get anyone anywhere useful. bug 432491
|
||||
okHost = okHost.replace(/^\*\./, "www.");
|
||||
/* case #1:
|
||||
* example.com uses an invalid security certificate.
|
||||
*
|
||||
* The certificate is only valid for www.example.com
|
||||
*
|
||||
* Make sure to include the "." ahead of thisHost so that
|
||||
* a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
|
||||
*
|
||||
* We'd normally just use a RegExp here except that we lack a
|
||||
* library function to escape them properly (bug 248062), and
|
||||
* domain names are famous for having '.' characters in them,
|
||||
* which would allow spurious and possibly hostile matches.
|
||||
*/
|
||||
if (okHost.endsWith("." + thisHost)) {
|
||||
href = proto + okHost;
|
||||
}
|
||||
/* case #2:
|
||||
* browser.garage.maemo.org uses an invalid security certificate.
|
||||
*
|
||||
* The certificate is only valid for garage.maemo.org
|
||||
*/
|
||||
if (thisHost.endsWith("." + okHost)) {
|
||||
href = proto + okHost;
|
||||
}
|
||||
|
||||
// If we set a link, meaning there's something helpful for
|
||||
// the user here, expand the section by default
|
||||
if (href && cssClass != "expertBadCert") {
|
||||
doc.getElementById("technicalContentText").style.display = "block";
|
||||
}
|
||||
|
||||
// Set the link if we want it.
|
||||
if (href) {
|
||||
let referrerlink = doc.createElement("a");
|
||||
referrerlink.append(subjectAltNamesList + "\n");
|
||||
referrerlink.title = subjectAltNamesList;
|
||||
referrerlink.id = "cert_domain_link";
|
||||
referrerlink.href = href;
|
||||
let fragment = BrowserUtils.getLocalizedFragment(
|
||||
doc,
|
||||
msgPrefix,
|
||||
referrerlink
|
||||
);
|
||||
technicalInfo.appendChild(fragment);
|
||||
} else {
|
||||
let fragment = BrowserUtils.getLocalizedFragment(
|
||||
doc,
|
||||
msgPrefix,
|
||||
subjectAltNamesList
|
||||
);
|
||||
technicalInfo.appendChild(fragment);
|
||||
}
|
||||
technicalInfo.append("\n");
|
||||
} else {
|
||||
let msg =
|
||||
gPipNSSBundle.GetStringFromName("certErrorMismatchMultiple") + "\n";
|
||||
for (let i = 0; i < numSubjectAltNames; i++) {
|
||||
msg += subjectAltNames[i];
|
||||
if (i != numSubjectAltNames - 1) {
|
||||
msg += ", ";
|
||||
}
|
||||
}
|
||||
technicalInfo.append(msg + "\n");
|
||||
}
|
||||
} else {
|
||||
let msg = gPipNSSBundle.formatStringFromName("certErrorMismatch", [
|
||||
hostString,
|
||||
]);
|
||||
technicalInfo.append(msg + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (securityInfo.isNotValidAtThisTime) {
|
||||
let nowTime = new Date().getTime() * 1000;
|
||||
let dateOptions = {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
};
|
||||
let now = new Services.intl.DateTimeFormat(undefined, dateOptions).format(
|
||||
new Date()
|
||||
);
|
||||
let msg = "";
|
||||
if (validity.notBefore) {
|
||||
if (nowTime > validity.notAfter) {
|
||||
msg +=
|
||||
gPipNSSBundle.formatStringFromName("certErrorExpiredNow", [
|
||||
validity.notAfterLocalTime,
|
||||
now,
|
||||
]) + "\n";
|
||||
} else {
|
||||
msg +=
|
||||
gPipNSSBundle.formatStringFromName("certErrorNotYetValidNow", [
|
||||
validity.notBeforeLocalTime,
|
||||
now,
|
||||
]) + "\n";
|
||||
}
|
||||
} else {
|
||||
// If something goes wrong, we assume the cert expired.
|
||||
msg +=
|
||||
gPipNSSBundle.formatStringFromName("certErrorExpiredNow", ["", now]) +
|
||||
"\n";
|
||||
}
|
||||
technicalInfo.append(msg);
|
||||
}
|
||||
technicalInfo.append("\n");
|
||||
|
||||
// Add link to certificate and error message.
|
||||
let errorCodeMsg = gPipNSSBundle.formatStringFromName(
|
||||
"certErrorCodePrefix3",
|
||||
[securityInfo.errorCodeString]
|
||||
);
|
||||
technicalInfo.append(errorCodeMsg);
|
||||
},
|
||||
|
||||
handleEvent(aEvent) {
|
||||
if (!this.isCertErrorSite) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aEvent.type != "AboutCertErrorLoad") {
|
||||
return;
|
||||
}
|
||||
|
||||
let ownerDoc = aEvent.originalTarget.ownerGlobal;
|
||||
let securityInfo =
|
||||
docShell.failedChannel && docShell.failedChannel.securityInfo;
|
||||
securityInfo
|
||||
.QueryInterface(Ci.nsITransportSecurityInfo)
|
||||
.QueryInterface(Ci.nsISerializable);
|
||||
this._setTechDetails(securityInfo, ownerDoc.location.href);
|
||||
},
|
||||
};
|
||||
AboutCertErrorListener.init();
|
||||
|
||||
// This is copied from desktop's tab-content.js. See bug 1153485 about sharing this code somehow.
|
||||
var AboutReaderListener = {
|
||||
_articlePromise: null,
|
||||
|
||||
_isLeavingReaderableReaderMode: false,
|
||||
|
||||
init: function() {
|
||||
addEventListener("AboutReaderContentLoaded", this, false, true);
|
||||
addEventListener("DOMContentLoaded", this, false);
|
||||
addEventListener("pageshow", this, false);
|
||||
addEventListener("pagehide", this, false);
|
||||
addMessageListener("Reader:ToggleReaderMode", this);
|
||||
addMessageListener("Reader:PushState", this);
|
||||
},
|
||||
|
||||
receiveMessage: function(message) {
|
||||
switch (message.name) {
|
||||
case "Reader:ToggleReaderMode":
|
||||
let url = content.document.location.href;
|
||||
if (!this.isAboutReader) {
|
||||
this._articlePromise = ReaderMode.parseDocument(
|
||||
content.document
|
||||
).catch(Cu.reportError);
|
||||
ReaderMode.enterReaderMode(docShell, content);
|
||||
} else {
|
||||
this._isLeavingReaderableReaderMode = this.isReaderableAboutReader;
|
||||
ReaderMode.leaveReaderMode(docShell, content);
|
||||
}
|
||||
break;
|
||||
|
||||
case "Reader:PushState":
|
||||
this.updateReaderButton(!!(message.data && message.data.isArticle));
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
get isAboutReader() {
|
||||
return content.document.documentURI.startsWith("about:reader");
|
||||
},
|
||||
|
||||
get isReaderableAboutReader() {
|
||||
return (
|
||||
this.isAboutReader && !content.document.documentElement.dataset.isError
|
||||
);
|
||||
},
|
||||
|
||||
get isErrorPage() {
|
||||
return (
|
||||
content.document.documentURI.startsWith("about:neterror") ||
|
||||
content.document.documentURI.startsWith("about:certerror") ||
|
||||
content.document.documentURI.startsWith("about:blocked")
|
||||
);
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
if (aEvent.originalTarget.defaultView != content) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (aEvent.type) {
|
||||
case "AboutReaderContentLoaded":
|
||||
if (!this.isAboutReader) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are restoring multiple reader mode tabs during session restore, duplicate "DOMContentLoaded"
|
||||
// events may be fired for the visible tab. The inital "DOMContentLoaded" may be received before the
|
||||
// document body is available, so we avoid instantiating an AboutReader object, expecting that a
|
||||
// valid message will follow. See bug 925983.
|
||||
if (content.document.body) {
|
||||
new AboutReader(global, content, this._articlePromise);
|
||||
this._articlePromise = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case "pagehide":
|
||||
// this._isLeavingReaderableReaderMode is used here to keep the Reader Mode icon
|
||||
// visible in the location bar when transitioning from reader-mode page
|
||||
// back to the source page.
|
||||
sendAsyncMessage("Reader:UpdateReaderButton", {
|
||||
isArticle: this._isLeavingReaderableReaderMode,
|
||||
});
|
||||
if (this._isLeavingReaderableReaderMode) {
|
||||
this._isLeavingReaderableReaderMode = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case "pageshow":
|
||||
// If a page is loaded from the bfcache, we won't get a "DOMContentLoaded"
|
||||
// event, so we need to rely on "pageshow" in this case.
|
||||
if (aEvent.persisted) {
|
||||
this.updateReaderButton();
|
||||
}
|
||||
break;
|
||||
case "DOMContentLoaded":
|
||||
this.updateReaderButton();
|
||||
break;
|
||||
}
|
||||
},
|
||||
updateReaderButton: function(forceNonArticle) {
|
||||
// Do not show Reader View icon on error pages (bug 1320900)
|
||||
if (this.isErrorPage) {
|
||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
|
||||
} else if (
|
||||
!Readerable.isEnabledForParseOnLoad ||
|
||||
this.isAboutReader ||
|
||||
!(content.document instanceof content.HTMLDocument) ||
|
||||
content.document.mozSyntheticDocument
|
||||
) {
|
||||
} else {
|
||||
this.scheduleReadabilityCheckPostPaint(forceNonArticle);
|
||||
}
|
||||
},
|
||||
|
||||
cancelPotentialPendingReadabilityCheck: function() {
|
||||
if (this._pendingReadabilityCheck) {
|
||||
removeEventListener("MozAfterPaint", this._pendingReadabilityCheck);
|
||||
delete this._pendingReadabilityCheck;
|
||||
}
|
||||
},
|
||||
|
||||
scheduleReadabilityCheckPostPaint: function(forceNonArticle) {
|
||||
if (this._pendingReadabilityCheck) {
|
||||
// We need to stop this check before we re-add one because we don't know
|
||||
// if forceNonArticle was true or false last time.
|
||||
this.cancelPotentialPendingReadabilityCheck();
|
||||
}
|
||||
this._pendingReadabilityCheck = this.onPaintWhenWaitedFor.bind(
|
||||
this,
|
||||
forceNonArticle
|
||||
);
|
||||
addEventListener("MozAfterPaint", this._pendingReadabilityCheck);
|
||||
},
|
||||
|
||||
onPaintWhenWaitedFor: function(forceNonArticle, event) {
|
||||
// In non-e10s, we'll get called for paints other than ours, and so it's
|
||||
// possible that this page hasn't been laid out yet, in which case we
|
||||
// should wait until we get an event that does relate to our layout. We
|
||||
// determine whether any of our content got painted by checking if there
|
||||
// are any painted rects.
|
||||
if (!event.clientRects.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.console.logStringMessage(`ON PAINT WHEN WAITED FOR\n`);
|
||||
this.cancelPotentialPendingReadabilityCheck();
|
||||
|
||||
// Only send updates when there are articles; there's no point updating with
|
||||
// |false| all the time.
|
||||
if (Readerable.isProbablyReaderable(content.document)) {
|
||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
|
||||
} else if (forceNonArticle) {
|
||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
|
||||
}
|
||||
},
|
||||
};
|
||||
AboutReaderListener.init();
|
||||
|
||||
Services.obs.notifyObservers(this, "tab-content-frameloader-created");
|
@ -1,114 +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/.
|
||||
|
||||
# LOCALIZATION NOTE: do not localize
|
||||
af=Afrikaans
|
||||
ak=Akan
|
||||
ar=عربي
|
||||
as=অসমীয়া
|
||||
ast-ES=Asturianu
|
||||
be=Беларуская
|
||||
bg=Български
|
||||
bn-BD=বাংলা (বাংলাদেশ)
|
||||
bn-IN=বাংলা (ভারত)
|
||||
br-FR=Brezhoneg
|
||||
ca=català
|
||||
ca-valencia=català (valencià)
|
||||
cs=Čeština
|
||||
cy=Cymraeg
|
||||
da=Dansk
|
||||
de=Deutsch
|
||||
de-AT=Deutsch (Österreich)
|
||||
de-CH=Deutsch (Schweiz)
|
||||
de-DE=Deutsch (Deutschland)
|
||||
el=Ελληνικά
|
||||
en-AU=English (Australian)
|
||||
en-CA=English (Canadian)
|
||||
en-GB=English (British)
|
||||
en-NZ=English (New Zealand)
|
||||
en-US=English (US)
|
||||
en-ZA=English (South African)
|
||||
eo=Esperanto
|
||||
es-AR=Español (de Argentina)
|
||||
es-CL=Español (de Chile)
|
||||
es-ES=Español (de España)
|
||||
es-MX=Español (de México)
|
||||
et=Eesti keel
|
||||
eu=Euskara
|
||||
fa=فارسی
|
||||
fi=suomi
|
||||
fr=Français
|
||||
fur-IT=Furlan
|
||||
fy-NL=Frysk
|
||||
ga-IE=Gaeilge
|
||||
gl=Galego
|
||||
gu-IN=ગુજરાતી
|
||||
he=עברית
|
||||
hi=हिन्दी
|
||||
hi-IN=हिन्दी (भारत)
|
||||
hr=Hrvatski
|
||||
hsb=Hornjoserbsce
|
||||
hu=Magyar
|
||||
hy-AM=Հայերեն
|
||||
id=Bahasa Indonesia
|
||||
is=íslenska
|
||||
it=Italiano
|
||||
ja=日本語
|
||||
ka=ქართული
|
||||
kk=Қазақ
|
||||
kn=ಕನ್ನಡ
|
||||
ko=한국어
|
||||
ku=Kurdî
|
||||
la=Latina
|
||||
lt=lietuvių
|
||||
lv=Latviešu
|
||||
mg=Malagasy
|
||||
mi=Māori (Aotearoa)
|
||||
mk=Македонски
|
||||
ml=മലയാളം
|
||||
mn=Монгол
|
||||
mr=मराठी
|
||||
nb-NO=Norsk bokmål
|
||||
ne-NP=नेपाली
|
||||
nl=Nederlands
|
||||
nn-NO=Norsk nynorsk
|
||||
nr=isiNdebele Sepumalanga
|
||||
nso=Sepedi
|
||||
oc=occitan (lengadocian)
|
||||
or=ଓଡ଼ିଆ
|
||||
pa-IN=ਪੰਜਾਬੀ
|
||||
pl=Polski
|
||||
pt-BR=Português (do Brasil)
|
||||
pt-PT=Português (Europeu)
|
||||
rm=rumantsch
|
||||
ro=română
|
||||
ru=Русский
|
||||
rw=Ikinyarwanda
|
||||
si=සිංහල
|
||||
sk=slovenčina
|
||||
sl=slovensko
|
||||
sq=Shqip
|
||||
sr=Српски
|
||||
sr-Latn=Srpski
|
||||
ss=Siswati
|
||||
st=Sesotho
|
||||
sv-SE=Svenska
|
||||
ta=தமிழ்
|
||||
ta-IN=தமிழ் (இந்தியா)
|
||||
ta-LK=தமிழ் (இலங்கை)
|
||||
te=తెలుగు
|
||||
th=ไทย
|
||||
tn=Setswana
|
||||
tr=Türkçe
|
||||
ts=Mutsonga
|
||||
tt-RU=Tatarça
|
||||
uk=Українська
|
||||
ur=اُردو
|
||||
ve=Tshivenḓa
|
||||
vi=Tiếng Việt
|
||||
wo=Wolof
|
||||
xh=isiXhosa
|
||||
zh-CN=中文 (简体)
|
||||
zh-TW=正體中文 (繁體)
|
||||
zu=isiZulu
|
@ -1,315 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % netErrorDTD
|
||||
SYSTEM "chrome://global/locale/netError.dtd">
|
||||
%netErrorDTD;
|
||||
<!ENTITY % globalDTD
|
||||
SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
]>
|
||||
|
||||
<!-- 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 xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=false;" />
|
||||
<title>&loadError.label;</title>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/netError.css" media="all" />
|
||||
<!-- If the location of the favicon is changed here, the FAVICON_ERRORPAGE_URL symbol in
|
||||
toolkit/components/places/src/nsFaviconService.h should be updated. -->
|
||||
<link rel="icon" type="image/png" id="favicon" sizes="64x64" href="chrome://browser/skin/images/errorpage-warning.png"/>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
// Error url MUST be formatted like this:
|
||||
// moz-neterror:page?e=error&u=url&d=desc
|
||||
//
|
||||
// or optionally, to specify an alternate CSS class to allow for
|
||||
// custom styling and favicon:
|
||||
//
|
||||
// moz-neterror:page?e=error&u=url&s=classname&d=desc
|
||||
|
||||
// Note that this file uses document.documentURI to get
|
||||
// the URL (with the format from above). This is because
|
||||
// document.location.href gets the current URI off the docshell,
|
||||
// which is the URL displayed in the location bar, i.e.
|
||||
// the URI that the user attempted to load.
|
||||
|
||||
function getErrorCode() {
|
||||
var url = document.documentURI;
|
||||
var error = url.search(/e\=/);
|
||||
var duffUrl = url.search(/\&u\=/);
|
||||
return decodeURIComponent(url.slice(error + 2, duffUrl));
|
||||
}
|
||||
|
||||
function getCSSClass() {
|
||||
var url = document.documentURI;
|
||||
var matches = url.match(/s\=([^&]+)\&/);
|
||||
// s is optional, if no match just return nothing
|
||||
if (!matches || matches.length < 2)
|
||||
return "";
|
||||
|
||||
// parenthetical match is the second entry
|
||||
return decodeURIComponent(matches[1]);
|
||||
}
|
||||
|
||||
function getDescription() {
|
||||
var url = document.documentURI;
|
||||
var desc = url.search(/d\=/);
|
||||
|
||||
// desc == -1 if not found; if so, return an empty string
|
||||
// instead of what would turn out to be portions of the URI
|
||||
if (desc == -1)
|
||||
return "";
|
||||
|
||||
return decodeURIComponent(url.slice(desc + 2));
|
||||
}
|
||||
|
||||
function retryThis(buttonEl) {
|
||||
// Note: The application may wish to handle switching off "offline mode"
|
||||
// before this event handler runs, but using a capturing event handler.
|
||||
|
||||
// Session history has the URL of the page that failed
|
||||
// to load, not the one of the error page. So, just call
|
||||
// reload(), which will also repost POST data correctly.
|
||||
try {
|
||||
location.reload();
|
||||
} catch (e) {
|
||||
// We probably tried to reload a URI that caused an exception to
|
||||
// occur; e.g. a nonexistent file.
|
||||
}
|
||||
}
|
||||
|
||||
function initPage() {
|
||||
var err = getErrorCode();
|
||||
|
||||
// if it's an unknown error or there's no title or description
|
||||
// defined, get the generic message
|
||||
var errTitle = document.getElementById("et_" + err);
|
||||
var errDesc = document.getElementById("ed_" + err);
|
||||
if (!errTitle || !errDesc) {
|
||||
errTitle = document.getElementById("et_generic");
|
||||
errDesc = document.getElementById("ed_generic");
|
||||
}
|
||||
|
||||
var title = document.getElementsByClassName("errorTitleText")[0];
|
||||
if (title) {
|
||||
title.parentNode.replaceChild(errTitle, title);
|
||||
// change id to the replaced child's id so styling works
|
||||
errTitle.classList.add("errorTitleText");
|
||||
}
|
||||
|
||||
var ld = document.getElementById("errorLongDesc");
|
||||
if (ld) {
|
||||
ld.parentNode.replaceChild(errDesc, ld);
|
||||
// change id to the replaced child's id so styling works
|
||||
errDesc.id = "errorLongDesc";
|
||||
}
|
||||
|
||||
// remove undisplayed errors to avoid bug 39098
|
||||
var errContainer = document.getElementById("errorContainer");
|
||||
errContainer.remove();
|
||||
|
||||
var className = getCSSClass();
|
||||
if (className && className != "expertBadCert") {
|
||||
// Associate a CSS class with the root of the page, if one was passed in,
|
||||
// to allow custom styling.
|
||||
// Not "expertBadCert" though, don't want to deal with the favicon
|
||||
document.documentElement.className = className;
|
||||
|
||||
// Also, if they specified a CSS class, they must supply their own
|
||||
// favicon. In order to trigger the browser to repaint though, we
|
||||
// need to remove/add the link element.
|
||||
var favicon = document.getElementById("favicon");
|
||||
var faviconParent = favicon.parentNode;
|
||||
faviconParent.removeChild(favicon);
|
||||
favicon.setAttribute("href", "chrome://global/skin/icons/" + className + "_favicon.png");
|
||||
faviconParent.appendChild(favicon);
|
||||
}
|
||||
if (className == "expertBadCert") {
|
||||
showSecuritySection();
|
||||
}
|
||||
|
||||
if (err == "remoteXUL") {
|
||||
// Remove the "Try again" button for remote XUL errors given that
|
||||
// it is useless.
|
||||
document.getElementById("errorTryAgain").style.display = "none";
|
||||
}
|
||||
|
||||
if (err == "cspBlocked") {
|
||||
// Remove the "Try again" button for CSP violations, since it's
|
||||
// almost certainly useless. (Bug 553180)
|
||||
document.getElementById("errorTryAgain").style.display = "none";
|
||||
}
|
||||
|
||||
if (err == "nssBadCert") {
|
||||
// Remove the "Try again" button for security exceptions, since it's
|
||||
// almost certainly useless.
|
||||
document.getElementById("errorTryAgain").style.display = "none";
|
||||
document.getElementById("errorPage").setAttribute("class", "certerror");
|
||||
} else {
|
||||
// Remove the override block for non-certificate errors. CSS-hiding
|
||||
// isn't good enough here, because of bug 39098
|
||||
var secOverride = document.getElementById("securityOverrideDiv");
|
||||
secOverride.remove();
|
||||
}
|
||||
|
||||
if (err != "nssBadCert" && err != "nssFailure2") {
|
||||
var sd = document.getElementById("errorShortDescText");
|
||||
if (sd) {
|
||||
sd.textContent = getDescription();
|
||||
}
|
||||
}
|
||||
|
||||
if (err == "inadequateSecurityError") {
|
||||
// Remove the "Try again" button for HTTP/2 inadequate security as it
|
||||
// is useless.
|
||||
document.getElementById("errorTryAgain").style.display = "none";
|
||||
|
||||
var container = document.getElementById("errorLongDesc");
|
||||
for (var span of container.querySelectorAll("span.hostname")) {
|
||||
span.textContent = document.location.hostname;
|
||||
}
|
||||
}
|
||||
|
||||
var event = new CustomEvent("AboutNetErrorLoad", {bubbles: true});
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function showSecuritySection() {
|
||||
// Swap link out, content in
|
||||
document.getElementById("securityOverrideContent").style.display = "";
|
||||
document.getElementById("securityOverrideLink").style.display = "none";
|
||||
}
|
||||
|
||||
function createLink(el, id, text) {
|
||||
var anchorEl = document.createElement("a");
|
||||
anchorEl.setAttribute("id", id);
|
||||
anchorEl.setAttribute("title", text);
|
||||
anchorEl.appendChild(document.createTextNode(text));
|
||||
el.appendChild(anchorEl);
|
||||
}
|
||||
]]></script>
|
||||
</head>
|
||||
|
||||
<body id="errorPage" dir="&locale.dir;">
|
||||
|
||||
<!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) -->
|
||||
<div id="errorContainer">
|
||||
<div id="errorTitlesContainer">
|
||||
<h1 id="et_generic">&generic.title;</h1>
|
||||
<h1 id="et_dnsNotFound">&dnsNotFound.title;</h1>
|
||||
<h1 id="et_fileNotFound">&fileNotFound.title;</h1>
|
||||
<h1 id="et_fileAccessDenied">&fileAccessDenied.title;</h1>
|
||||
<h1 id="et_malformedURI">&malformedURI.title;</h1>
|
||||
<h1 id="et_unknownProtocolFound">&unknownProtocolFound.title;</h1>
|
||||
<h1 id="et_connectionFailure">&connectionFailure.title;</h1>
|
||||
<h1 id="et_netTimeout">&netTimeout.title;</h1>
|
||||
<h1 id="et_redirectLoop">&redirectLoop.title;</h1>
|
||||
<h1 id="et_unknownSocketType">&unknownSocketType.title;</h1>
|
||||
<h1 id="et_netReset">&netReset.title;</h1>
|
||||
<h1 id="et_notCached">¬Cached.title;</h1>
|
||||
|
||||
<!-- Since Fennec not yet have offline mode, change the title to
|
||||
connectionFailure to prevent confusion -->
|
||||
<h1 id="et_netOffline">&connectionFailure.title;</h1>
|
||||
|
||||
<h1 id="et_netInterrupt">&netInterrupt.title;</h1>
|
||||
<h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
|
||||
<h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
|
||||
<h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
|
||||
<h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
|
||||
<h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
|
||||
<h1 id="et_nssFailure2">&nssFailure2.title;</h1>
|
||||
<h1 id="et_nssBadCert">&nssBadCert.title;</h1>
|
||||
<h1 id="et_cspBlocked">&cspBlocked.title;</h1>
|
||||
<h1 id="et_remoteXUL">&remoteXUL.title;</h1>
|
||||
<h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1>
|
||||
<h1 id="et_sslv3Used">&sslv3Used.title;</h1>
|
||||
<h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1>
|
||||
<h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1>
|
||||
<h1 id="et_networkProtocolError">&networkProtocolError.title;</h1>
|
||||
</div>
|
||||
<div id="errorDescriptionsContainer">
|
||||
<div id="ed_generic">&generic.longDesc;</div>
|
||||
<div id="ed_dnsNotFound">&dnsNotFound.longDesc4;</div>
|
||||
<div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
|
||||
<div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div>
|
||||
<div id="ed_malformedURI">&malformedURI.longDesc2;</div>
|
||||
<div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div>
|
||||
<div id="ed_connectionFailure">&connectionFailure.longDesc2;</div>
|
||||
<div id="ed_netTimeout">&netTimeout.longDesc2;</div>
|
||||
<div id="ed_redirectLoop">&redirectLoop.longDesc;</div>
|
||||
<div id="ed_unknownSocketType">&unknownSocketType.longDesc;</div>
|
||||
<div id="ed_netReset">&netReset.longDesc2;</div>
|
||||
<div id="ed_notCached">¬Cached.longDesc;</div>
|
||||
|
||||
<!-- Change longDesc from netOffline to connectionFailure,
|
||||
suggesting user to check their wifi/cell_data connection -->
|
||||
<div id="ed_netOffline">&connectionFailure.longDesc2;</div>
|
||||
|
||||
<div id="ed_netInterrupt">&netInterrupt.longDesc2;</div>
|
||||
<div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
|
||||
<div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc3;</div>
|
||||
<div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
|
||||
<div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
|
||||
<div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
|
||||
<div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
|
||||
<div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
|
||||
<div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
|
||||
<div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
|
||||
<div id="ed_corruptedContentErrorv2">&corruptedContentErrorv2.longDesc;</div>
|
||||
<div id="ed_sslv3Used">&sslv3Used.longDesc;</div>
|
||||
<div id="ed_weakCryptoUsed">&weakCryptoUsed.longDesc;</div>
|
||||
<div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div>
|
||||
<div id="ed_networkProtocolError">&networkProtocolError.longDesc;</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
||||
<div id="errorPageContainer">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div id="errorTitle">
|
||||
<h1 class="errorTitleText" />
|
||||
</div>
|
||||
|
||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
||||
<div id="errorLongContent">
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p id="errorShortDescText" />
|
||||
</div>
|
||||
|
||||
<!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
|
||||
<div id="errorLongDesc" />
|
||||
|
||||
<!-- Override section - For ssl errors only. Removed on init for other
|
||||
error types. -->
|
||||
<div id="securityOverrideDiv">
|
||||
<a id="securityOverrideLink" href="javascript:showSecuritySection();" >&securityOverride.linkText;</a>
|
||||
<div id="securityOverrideContent" style="display: none;">&securityOverride.warningContent;</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Retry Button -->
|
||||
<button id="errorTryAgain" onclick="retryThis(this);">&retry.label;</button>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
- Note: It is important to run the script this way, instead of using
|
||||
- an onload handler. This is because error pages are loaded as
|
||||
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
|
||||
-->
|
||||
<script type="application/javascript">initPage();</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,74 +0,0 @@
|
||||
#filter substitution
|
||||
# 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/.
|
||||
|
||||
chrome.jar:
|
||||
% content browser %content/ contentaccessible=yes
|
||||
|
||||
* content/about.xhtml (content/about.xhtml)
|
||||
* content/about.js (content/about.js)
|
||||
content/content.js (content/content.js)
|
||||
content/aboutAddons.xhtml (content/aboutAddons.xhtml)
|
||||
content/aboutAddons.js (content/aboutAddons.js)
|
||||
content/aboutCertError.xhtml (content/aboutCertError.xhtml)
|
||||
content/aboutDownloads.xhtml (content/aboutDownloads.xhtml)
|
||||
content/aboutDownloads.js (content/aboutDownloads.js)
|
||||
content/aboutPrivateBrowsing.xhtml (content/aboutPrivateBrowsing.xhtml)
|
||||
content/aboutPrivateBrowsing.js (content/aboutPrivateBrowsing.js)
|
||||
content/Reader.js (content/Reader.js)
|
||||
content/aboutHome.xhtml (content/aboutHome.xhtml)
|
||||
content/aboutRights.xhtml (content/aboutRights.xhtml)
|
||||
content/blockedSite.xhtml (content/blockedSite.xhtml)
|
||||
content/languages.properties (content/languages.properties)
|
||||
content/browser.xul (content/browser.xul)
|
||||
content/browser.css (content/browser.css)
|
||||
content/browser.js (content/browser.js)
|
||||
content/PresentationView.xul (content/PresentationView.xul)
|
||||
content/PresentationView.js (content/PresentationView.js)
|
||||
content/netError.xhtml (content/netError.xhtml)
|
||||
content/EmbedRT.js (content/EmbedRT.js)
|
||||
content/MemoryObserver.js (content/MemoryObserver.js)
|
||||
content/ConsoleAPI.js (content/ConsoleAPI.js)
|
||||
content/PrintHelper.js (content/PrintHelper.js)
|
||||
content/OfflineApps.js (content/OfflineApps.js)
|
||||
content/MasterPassword.js (content/MasterPassword.js)
|
||||
content/FindHelper.js (content/FindHelper.js)
|
||||
content/PermissionsHelper.js (content/PermissionsHelper.js)
|
||||
content/FeedHandler.js (content/FeedHandler.js)
|
||||
content/Feedback.js (content/Feedback.js)
|
||||
content/Linkify.js (content/Linkify.js)
|
||||
content/CastingApps.js (content/CastingApps.js)
|
||||
content/RemoteDebugger.js (content/RemoteDebugger.js)
|
||||
content/aboutAccounts.xhtml (content/aboutAccounts.xhtml)
|
||||
content/aboutAccounts.js (content/aboutAccounts.js)
|
||||
content/aboutExperiments.xhtml (content/aboutExperiments.xhtml)
|
||||
content/aboutExperiments.js (content/aboutExperiments.js)
|
||||
content/aboutLogins.xhtml (content/aboutLogins.xhtml)
|
||||
content/aboutLogins.js (content/aboutLogins.js)
|
||||
content/ExtensionPermissions.js (content/ExtensionPermissions.js)
|
||||
|
||||
% override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
|
||||
% override chrome://mozapps/content/extensions/extensions.xul chrome://browser/content/aboutAddons.xhtml
|
||||
|
||||
# L10n resource overrides.
|
||||
% override chrome://global/locale/aboutReader.properties chrome://browser/locale/overrides/aboutReader.properties
|
||||
% override chrome://global/locale/charsetMenu.properties chrome://browser/locale/overrides/charsetMenu.properties
|
||||
% override chrome://global/locale/commonDialogs.properties chrome://browser/locale/overrides/commonDialogs.properties
|
||||
% override chrome://global/locale/intl.properties chrome://browser/locale/overrides/intl.properties
|
||||
% override chrome://global/locale/intl.css chrome://browser/locale/overrides/intl.css
|
||||
% override chrome://global/locale/search/search.properties chrome://browser/locale/overrides/search/search.properties
|
||||
% override chrome://pluginproblem/locale/pluginproblem.dtd chrome://browser/locale/overrides/plugins/pluginproblem.dtd
|
||||
% override chrome://global/locale/mozilla.dtd chrome://browser/locale/overrides/global/mozilla.dtd
|
||||
% override chrome://global/locale/aboutWebrtc.properties chrome://browser/locale/overrides/global/aboutWebrtc.properties
|
||||
|
||||
# overrides for dom l10n, also for en-US
|
||||
# keep this file list in sync with filter.py
|
||||
% override chrome://global/locale/global.dtd chrome://browser/locale/overrides/global.dtd
|
||||
% override chrome://global/locale/AccessFu.properties chrome://browser/locale/overrides/AccessFu.properties
|
||||
% override chrome://global/locale/dom/dom.properties chrome://browser/locale/overrides/dom/dom.properties
|
||||
% override chrome://global/locale/plugins.properties chrome://browser/locale/overrides/plugins.properties
|
||||
|
||||
# mobile/locales/jar.mn resources and overrides
|
||||
% override chrome://global/locale/netError.dtd chrome://browser/locale/netError.dtd
|
||||
% override chrome://global/locale/appstrings.properties chrome://browser/locale/appstrings.properties
|
@ -18,5 +18,3 @@ DEFINES['PACKAGE'] = 'browser'
|
||||
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
||||
DEFINES['MOZ_APP_VERSION_DISPLAY'] = CONFIG['MOZ_APP_VERSION_DISPLAY']
|
||||
DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
@ -1,16 +0,0 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Firefox for Android', 'General')
|
||||
|
||||
DIRS += [
|
||||
'webcompat'
|
||||
]
|
||||
if not CONFIG['MOZ_UPDATE_CHANNEL'] in ('release', 'esr'):
|
||||
DIRS += [
|
||||
'report-site-issue'
|
||||
]
|
@ -1,69 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"rules": {
|
||||
// Rules from the mozilla plugin
|
||||
"mozilla/balanced-listeners": "error",
|
||||
"mozilla/no-aArgs": "error",
|
||||
"mozilla/var-only-at-top-level": "error",
|
||||
|
||||
"valid-jsdoc": ["error", {
|
||||
"prefer": {
|
||||
"return": "returns",
|
||||
},
|
||||
"preferType": {
|
||||
"Boolean": "boolean",
|
||||
"Number": "number",
|
||||
"String": "string",
|
||||
"bool": "boolean",
|
||||
},
|
||||
"requireParamDescription": false,
|
||||
"requireReturn": false,
|
||||
"requireReturnDescription": false,
|
||||
}],
|
||||
|
||||
// No expressions where a statement is expected
|
||||
"no-unused-expressions": "error",
|
||||
|
||||
// No declaring variables that are never used
|
||||
"no-unused-vars": "error",
|
||||
|
||||
// Disallow using variables outside the blocks they are defined (especially
|
||||
// since only let and const are used, see "no-var").
|
||||
"block-scoped-var": "error",
|
||||
|
||||
// Warn about cyclomatic complexity in functions.
|
||||
"complexity": ["error", {"max": 26}],
|
||||
|
||||
// Maximum depth callbacks can be nested.
|
||||
"max-nested-callbacks": ["error", 4],
|
||||
|
||||
// Allow the console API aside from console.log.
|
||||
"no-console": ["error", {allow: ["error", "info", "trace", "warn"]}],
|
||||
|
||||
// Disallow fallthrough of case statements, except if there is a comment.
|
||||
"no-fallthrough": "error",
|
||||
|
||||
// Disallow use of multiline strings (use template strings instead).
|
||||
"no-multi-str": "error",
|
||||
|
||||
// Disallow usage of __proto__ property.
|
||||
"no-proto": "error",
|
||||
|
||||
// Disallow use of assignment in return statement. It is preferable for a
|
||||
// single line of code to have only one easily predictable effect.
|
||||
"no-return-assign": "error",
|
||||
|
||||
// Require use of the second argument for parseInt().
|
||||
"radix": "error",
|
||||
|
||||
// Require "use strict" to be defined globally in the script.
|
||||
"strict": ["error", "global"],
|
||||
|
||||
// Disallow Yoda conditions (where literal value comes first).
|
||||
"yoda": "error",
|
||||
|
||||
// Disallow function or variable declarations in nested blocks
|
||||
"no-inner-declarations": "error",
|
||||
},
|
||||
};
|
@ -1,306 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* globals browser */
|
||||
|
||||
const Config = {
|
||||
newIssueEndpoint: "https://webcompat.com/issues/new",
|
||||
newIssueEndpointPref: "newIssueEndpoint",
|
||||
screenshotFormat: {
|
||||
format: "jpeg",
|
||||
quality: 75,
|
||||
},
|
||||
};
|
||||
|
||||
const FRAMEWORK_KEYS = ["hasFastClick", "hasMobify", "hasMarfeel"];
|
||||
|
||||
// If parental controls are on, we don't activate (that is, we don't show our
|
||||
// menu item or prompt the user when they use "request desktop site".
|
||||
browser.browserInfo.getParentalControlsEnabled().then(enabled => {
|
||||
if (enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
browser.aboutConfigPrefs.onEndpointPrefChange.addListener(checkEndpointPref);
|
||||
checkEndpointPref();
|
||||
|
||||
activateMenuItem();
|
||||
activateDesktopViewPrompts();
|
||||
});
|
||||
|
||||
function activateDesktopViewPrompts() {
|
||||
Promise.all([
|
||||
browser.l10n.getMessage("webcompat.reportDesktopMode.message"),
|
||||
browser.l10n.getMessage("webcompat.reportDesktopModeYes.label"),
|
||||
])
|
||||
.then(([message, button]) => {
|
||||
browser.tabExtras.onDesktopSiteRequested.addListener(async tabId => {
|
||||
browser.tabs
|
||||
.get(tabId)
|
||||
.then(tab => {
|
||||
browser.snackbars
|
||||
.show(message, button)
|
||||
.then(() => {
|
||||
reportForTab(tab);
|
||||
})
|
||||
.catch(() => {});
|
||||
})
|
||||
.catch(() => {});
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function activateMenuItem() {
|
||||
browser.nativeMenu.show();
|
||||
|
||||
browser.l10n
|
||||
.getMessage("webcompat.menu.name")
|
||||
.then(label => {
|
||||
// We use Fennec NativeMenus because its BrowserAction implementation
|
||||
// lacks support for enabling/disabling its items.
|
||||
browser.nativeMenu.setLabel(label);
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
browser.nativeMenu.onClicked.addListener(async () => {
|
||||
const tabs = await browser.tabs.query({ active: true });
|
||||
reportForTab(tabs[0]);
|
||||
});
|
||||
|
||||
async function updateMenuItem(url) {
|
||||
if (isReportableUrl(url)) {
|
||||
await browser.nativeMenu.enable();
|
||||
} else {
|
||||
await browser.nativeMenu.disable();
|
||||
}
|
||||
}
|
||||
|
||||
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if ("url" in changeInfo && tab.active) {
|
||||
updateMenuItem(tab.url);
|
||||
}
|
||||
});
|
||||
|
||||
browser.tabs.onActivated.addListener(({ tabId }) => {
|
||||
browser.tabs
|
||||
.get(tabId)
|
||||
.then(({ url }) => {
|
||||
updateMenuItem(url);
|
||||
})
|
||||
.catch(() => {
|
||||
updateMenuItem("about"); // So the action is disabled
|
||||
});
|
||||
});
|
||||
|
||||
browser.tabs
|
||||
.query({ active: true })
|
||||
.then(tabs => {
|
||||
updateMenuItem(tabs[0].url);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function isReportableUrl(url) {
|
||||
return (
|
||||
url &&
|
||||
!(
|
||||
url.startsWith("about") ||
|
||||
url.startsWith("chrome") ||
|
||||
url.startsWith("file") ||
|
||||
url.startsWith("resource")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function reportForTab(tab) {
|
||||
return getWebCompatInfoForTab(tab)
|
||||
.then(async info => {
|
||||
return openWebCompatTab(info, tab.incognito);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Report Site Issue: unexpected error", err);
|
||||
});
|
||||
}
|
||||
|
||||
async function checkEndpointPref() {
|
||||
const value = await browser.aboutConfigPrefs.getEndpointPref();
|
||||
if (value === undefined) {
|
||||
browser.aboutConfigPrefs.setEndpointPref(Config.newIssueEndpoint);
|
||||
} else {
|
||||
Config.newIssueEndpoint = value;
|
||||
}
|
||||
}
|
||||
|
||||
function hasFastClickPageScript() {
|
||||
const win = window.wrappedJSObject;
|
||||
|
||||
if (win.FastClick) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const property in win) {
|
||||
try {
|
||||
const proto = win[property].prototype;
|
||||
if (proto && proto.needsClick) {
|
||||
return true;
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasMobifyPageScript() {
|
||||
const win = window.wrappedJSObject;
|
||||
return !!(win.Mobify && win.Mobify.Tag);
|
||||
}
|
||||
|
||||
function hasMarfeelPageScript() {
|
||||
const win = window.wrappedJSObject;
|
||||
return !!win.marfeel;
|
||||
}
|
||||
|
||||
function checkForFrameworks(tabId) {
|
||||
return browser.tabs
|
||||
.executeScript(tabId, {
|
||||
code: `
|
||||
(function() {
|
||||
${hasFastClickPageScript};
|
||||
${hasMobifyPageScript};
|
||||
${hasMarfeelPageScript};
|
||||
|
||||
const result = {
|
||||
hasFastClick: hasFastClickPageScript(),
|
||||
hasMobify: hasMobifyPageScript(),
|
||||
hasMarfeel: hasMarfeelPageScript(),
|
||||
}
|
||||
|
||||
return result;
|
||||
})();
|
||||
`,
|
||||
})
|
||||
.then(([results]) => results)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
function getWebCompatInfoForTab(tab) {
|
||||
const { id, windiwId, url } = tab;
|
||||
return Promise.all([
|
||||
browser.browserInfo.getBlockList(),
|
||||
browser.browserInfo.getBuildID(),
|
||||
browser.browserInfo.getGraphicsPrefs(),
|
||||
browser.browserInfo.getUpdateChannel(),
|
||||
browser.browserInfo.hasTouchScreen(),
|
||||
browser.tabExtras.getWebcompatInfo(id),
|
||||
checkForFrameworks(id),
|
||||
browser.tabs
|
||||
.captureVisibleTab(windiwId, Config.screenshotFormat)
|
||||
.catch(e => {
|
||||
console.error("Report Site Issue: getting a screenshot failed", e);
|
||||
return Promise.resolve(undefined);
|
||||
}),
|
||||
]).then(
|
||||
([
|
||||
blockList,
|
||||
buildID,
|
||||
graphicsPrefs,
|
||||
channel,
|
||||
hasTouchScreen,
|
||||
frameInfo,
|
||||
frameworks,
|
||||
screenshot,
|
||||
]) => {
|
||||
if (channel !== "linux") {
|
||||
delete graphicsPrefs["layers.acceleration.force-enabled"];
|
||||
}
|
||||
|
||||
const consoleLog = frameInfo.log;
|
||||
delete frameInfo.log;
|
||||
|
||||
return Object.assign(frameInfo, {
|
||||
tabId: id,
|
||||
blockList,
|
||||
details: Object.assign(graphicsPrefs, {
|
||||
buildID,
|
||||
channel,
|
||||
consoleLog,
|
||||
frameworks,
|
||||
hasTouchScreen,
|
||||
"mixed active content blocked":
|
||||
frameInfo.hasMixedActiveContentBlocked,
|
||||
"mixed passive content blocked":
|
||||
frameInfo.hasMixedDisplayContentBlocked,
|
||||
"tracking content blocked": frameInfo.hasTrackingContentBlocked
|
||||
? `true (${blockList})`
|
||||
: "false",
|
||||
}),
|
||||
screenshot,
|
||||
url,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function stripNonASCIIChars(str) {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return str.replace(/[^\x00-\x7F]/g, "");
|
||||
}
|
||||
|
||||
async function openWebCompatTab(compatInfo, usePrivateTab) {
|
||||
const url = new URL(Config.newIssueEndpoint);
|
||||
const { details } = compatInfo;
|
||||
const params = {
|
||||
url: `${compatInfo.url}`,
|
||||
utm_source: "mobile-reporter",
|
||||
utm_campaign: "report-site-issue-button",
|
||||
src: "mobile-reporter",
|
||||
details,
|
||||
extra_labels: [],
|
||||
};
|
||||
|
||||
for (let framework of FRAMEWORK_KEYS) {
|
||||
if (details.frameworks[framework]) {
|
||||
params.details[framework] = true;
|
||||
params.extra_labels.push(
|
||||
framework.replace(/^has/, "type-").toLowerCase()
|
||||
);
|
||||
}
|
||||
}
|
||||
delete details.frameworks;
|
||||
|
||||
if (details["gfx.webrender.all"] || details["gfx.webrender.enabled"]) {
|
||||
params.extra_labels.push("type-webrender-enabled");
|
||||
}
|
||||
if (compatInfo.hasTrackingContentBlocked) {
|
||||
params.extra_labels.push(
|
||||
`type-tracking-protection-${compatInfo.blockList}`
|
||||
);
|
||||
}
|
||||
|
||||
// Need custom API for private tabs until https://bugzil.la/1372178 is fixed
|
||||
const tab = usePrivateTab
|
||||
? await browser.tabExtras.createPrivateTab()
|
||||
: await browser.tabs.create({ url: "about:blank" });
|
||||
const json = stripNonASCIIChars(JSON.stringify(params));
|
||||
await browser.tabExtras.loadURIWithPostData(
|
||||
tab.id,
|
||||
url.href,
|
||||
json,
|
||||
"application/json"
|
||||
);
|
||||
await browser.tabs.executeScript(tab.id, {
|
||||
runAt: "document_end",
|
||||
code: `(function() {
|
||||
async function sendScreenshot(dataURI) {
|
||||
const res = await fetch(dataURI);
|
||||
const blob = await res.blob();
|
||||
postMessage(blob, "${url.origin}");
|
||||
}
|
||||
sendScreenshot("${compatInfo.screenshot}");
|
||||
})()`,
|
||||
});
|
||||
}
|
@ -1,41 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global ExtensionAPI, ExtensionCommon */
|
||||
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
this.aboutConfigPrefs = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
const EventManager = ExtensionCommon.EventManager;
|
||||
const extensionIDBase = context.extension.id.split("@")[0];
|
||||
const endpointPrefName = `extensions.${extensionIDBase}.newIssueEndpoint`;
|
||||
|
||||
return {
|
||||
aboutConfigPrefs: {
|
||||
onEndpointPrefChange: new EventManager({
|
||||
context,
|
||||
name: "aboutConfigPrefs.onEndpointPrefChange",
|
||||
register: fire => {
|
||||
const callback = () => {
|
||||
fire.async().catch(() => {}); // ignore Message Manager disconnects
|
||||
};
|
||||
Services.prefs.addObserver(endpointPrefName, callback);
|
||||
return () => {
|
||||
Services.prefs.removeObserver(endpointPrefName, callback);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
async getEndpointPref() {
|
||||
return Services.prefs.getStringPref(endpointPrefName, undefined);
|
||||
},
|
||||
async setEndpointPref(value) {
|
||||
Services.prefs.setStringPref(endpointPrefName, value);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
@ -1,35 +0,0 @@
|
||||
[
|
||||
{
|
||||
"namespace": "aboutConfigPrefs",
|
||||
"description": "experimental API extension to allow access to about:config preferences",
|
||||
"events": [
|
||||
{
|
||||
"name": "onEndpointPrefChange",
|
||||
"type": "function",
|
||||
"parameters": []
|
||||
}
|
||||
],
|
||||
"functions": [
|
||||
{
|
||||
"name": "getEndpointPref",
|
||||
"type": "function",
|
||||
"description": "Get the endpoint preference's value",
|
||||
"parameters": [],
|
||||
"async": true
|
||||
},
|
||||
{
|
||||
"name": "setEndpointPref",
|
||||
"type": "function",
|
||||
"description": "Set the endpoint preference's value",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "string",
|
||||
"description": "The new value"
|
||||
}
|
||||
],
|
||||
"async": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,72 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global ExtensionAPI */
|
||||
|
||||
var { AppConstants } = ChromeUtils.import(
|
||||
"resource://gre/modules/AppConstants.jsm"
|
||||
);
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const gParentalControls = (function() {
|
||||
if ("@mozilla.org/parental-controls-service;1" in Cc) {
|
||||
return Cc["@mozilla.org/parental-controls-service;1"].createInstance(
|
||||
Ci.nsIParentalControlsService
|
||||
);
|
||||
}
|
||||
return { parentalControlsEnabled: false };
|
||||
})();
|
||||
|
||||
this.browserInfo = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
return {
|
||||
browserInfo: {
|
||||
async getGraphicsPrefs() {
|
||||
const prefs = {};
|
||||
for (const [name, dflt] of Object.entries({
|
||||
"layers.acceleration.force-enabled": false,
|
||||
"gfx.webrender.all": false,
|
||||
"gfx.webrender.blob-images": true,
|
||||
"gfx.webrender.enabled": false,
|
||||
"image.mem.shared": true,
|
||||
})) {
|
||||
prefs[name] = Services.prefs.getBoolPref(name, dflt);
|
||||
}
|
||||
return prefs;
|
||||
},
|
||||
async getAppVersion() {
|
||||
return AppConstants.MOZ_APP_VERSION;
|
||||
},
|
||||
async getBlockList() {
|
||||
const trackingTable = Services.prefs.getCharPref(
|
||||
"urlclassifier.trackingTable"
|
||||
);
|
||||
// If content-track-digest256 is in the tracking table,
|
||||
// the user has enabled the strict list.
|
||||
return trackingTable.includes("content") ? "strict" : "basic";
|
||||
},
|
||||
async getBuildID() {
|
||||
return Services.appinfo.appBuildID;
|
||||
},
|
||||
async getUpdateChannel() {
|
||||
return AppConstants.MOZ_UPDATE_CHANNEL;
|
||||
},
|
||||
async getParentalControlsEnabled() {
|
||||
return gParentalControls.parentalControlsEnabled;
|
||||
},
|
||||
async getPlatform() {
|
||||
return AppConstants.platform;
|
||||
},
|
||||
async hasTouchScreen() {
|
||||
const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(
|
||||
Ci.nsIGfxInfo
|
||||
);
|
||||
return gfxInfo.getInfo().ApzTouchInput == 1;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
@ -1,64 +0,0 @@
|
||||
[
|
||||
{
|
||||
"namespace": "browserInfo",
|
||||
"description": "experimental API extensions to get browser info not exposed via web APIs",
|
||||
"functions": [
|
||||
{
|
||||
"name": "getAppVersion",
|
||||
"type": "function",
|
||||
"description": "Gets the app version",
|
||||
"parameters": [],
|
||||
"async": true
|
||||
},
|
||||
{
|
||||
"name": "getBlockList",
|
||||
"type": "function",
|
||||
"description": "Gets the current blocklist",
|
||||
"parameters": [],
|
||||
"async": true
|
||||
},
|
||||
{
|
||||
"name": "getBuildID",
|
||||
"type": "function",
|
||||
"description": "Gets the build ID",
|
||||
"parameters": [],
|
||||
"async": true
|
||||
},
|
||||
{
|
||||
"name": "getGraphicsPrefs",
|
||||
"type": "function",
|
||||
"description": "Gets interesting about:config prefs for graphics",
|
||||
"parameters": [],
|
||||
"async": true
|
||||
},
|
||||
{
|
||||
"name": "getParentalControlsEnabled",
|
||||
"type": "function",
|
||||
"description": "Gets whether the Parent Controls are enabled",
|
||||
"parameters": [],
|
||||
"async": true
|
||||
},
|
||||
{
|
||||
"name": "getPlatform",
|
||||
"type": "function",
|
||||
"description": "Gets the platform",
|
||||
"parameters": [],
|
||||
"async": true
|
||||
},
|
||||
{
|
||||
"name": "getUpdateChannel",
|
||||
"type": "function",
|
||||
"description": "Gets the update channel",
|
||||
"parameters": [],
|
||||
"async": true
|
||||
},
|
||||
{
|
||||
"name": "hasTouchScreen",
|
||||
"type": "function",
|
||||
"description": "Gets whether a touchscreen is present",
|
||||
"parameters": [],
|
||||
"async": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,33 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global ExtensionAPI, XPCOMUtils */
|
||||
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "l10nStrings", function() {
|
||||
return Services.strings.createBundle(
|
||||
"chrome://browser/locale/webcompatReporter.properties"
|
||||
);
|
||||
});
|
||||
|
||||
let l10nManifest;
|
||||
|
||||
this.l10n = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
return {
|
||||
l10n: {
|
||||
getMessage(name) {
|
||||
try {
|
||||
return Promise.resolve(l10nStrings.GetStringFromName(name));
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
@ -1,19 +0,0 @@
|
||||
[
|
||||
{
|
||||
"namespace": "l10n",
|
||||
"description": "A stop-gap L10N API only meant to be used until a Fluent-based API is added in bug 1425104",
|
||||
"functions": [
|
||||
{
|
||||
"name": "getMessage",
|
||||
"type": "function",
|
||||
"description": "Gets the message with the given name",
|
||||
"parameters": [{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"description": "The name of the message"
|
||||
}],
|
||||
"async": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,65 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global ExtensionAPI, ExtensionCommon */
|
||||
|
||||
const {
|
||||
Management: {
|
||||
global: { windowTracker },
|
||||
},
|
||||
} = ChromeUtils.import("resource://gre/modules/Extension.jsm", null);
|
||||
|
||||
function getNativeWindow() {
|
||||
return windowTracker.topWindow.NativeWindow;
|
||||
}
|
||||
|
||||
const clickHandlers = new ExtensionCommon.EventEmitter();
|
||||
|
||||
const menuItem = getNativeWindow().menu.add({
|
||||
name: "Report site issue",
|
||||
callback: () => {
|
||||
clickHandlers.emit("click");
|
||||
},
|
||||
enabled: false,
|
||||
visible: false,
|
||||
});
|
||||
|
||||
this.nativeMenu = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
return {
|
||||
nativeMenu: {
|
||||
onClicked: new ExtensionCommon.EventManager({
|
||||
context,
|
||||
name: "nativeMenu.onClicked",
|
||||
register: fire => {
|
||||
const callback = () => {
|
||||
fire.async().catch(() => {}); // ignore Message Manager disconnects
|
||||
};
|
||||
clickHandlers.on("click", callback);
|
||||
return () => {
|
||||
clickHandlers.off("click", callback);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
async disable() {
|
||||
getNativeWindow().menu.update(menuItem, { enabled: false });
|
||||
},
|
||||
async enable() {
|
||||
getNativeWindow().menu.update(menuItem, { enabled: true });
|
||||
},
|
||||
async hide() {
|
||||
getNativeWindow().menu.update(menuItem, { visible: false });
|
||||
},
|
||||
async show() {
|
||||
getNativeWindow().menu.update(menuItem, { visible: true });
|
||||
},
|
||||
async setLabel(label) {
|
||||
getNativeWindow().menu.update(menuItem, { name: label });
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
@ -1,53 +0,0 @@
|
||||
[
|
||||
{
|
||||
"namespace": "nativeMenu",
|
||||
"description": "experimental extension wrapping around a Fennec NativeMenu",
|
||||
"events": [
|
||||
{
|
||||
"name": "onClicked",
|
||||
"type": "function",
|
||||
"parameters": []
|
||||
}
|
||||
],
|
||||
"functions": [
|
||||
{
|
||||
"name": "disable",
|
||||
"type": "function",
|
||||
"async": true,
|
||||
"description": "Disable the addon's menu item",
|
||||
"parameters": []
|
||||
},
|
||||
{
|
||||
"name": "enable",
|
||||
"type": "function",
|
||||
"async": true,
|
||||
"description": "Enable the addon's menu item",
|
||||
"parameters": []
|
||||
},
|
||||
{
|
||||
"name": "hide",
|
||||
"type": "function",
|
||||
"async": true,
|
||||
"description": "Hide the addon's menu item",
|
||||
"parameters": []
|
||||
},
|
||||
{
|
||||
"name": "show",
|
||||
"type": "function",
|
||||
"async": true,
|
||||
"description": "Show the addon's menu item",
|
||||
"parameters": []
|
||||
},
|
||||
{
|
||||
"name": "setLabel",
|
||||
"type": "function",
|
||||
"async": true,
|
||||
"description": "Set the label of the addon's menu item",
|
||||
"parameters": [{
|
||||
"name": "label",
|
||||
"type": "string"
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,43 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global ExtensionAPI */
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"clearTimeout",
|
||||
"resource://gre/modules/Timer.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"setTimeout",
|
||||
"resource://gre/modules/Timer.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Snackbars",
|
||||
"resource://gre/modules/Snackbars.jsm"
|
||||
);
|
||||
|
||||
this.snackbars = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
return {
|
||||
snackbars: {
|
||||
show(message, button) {
|
||||
return new Promise((callback, rejection) => {
|
||||
Snackbars.show(message, Snackbars.LENGTH_LONG, {
|
||||
action: {
|
||||
label: button,
|
||||
callback,
|
||||
rejection,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
@ -1,21 +0,0 @@
|
||||
[
|
||||
{
|
||||
"namespace": "snackbars",
|
||||
"description": "experimental API extensions for prompting the user via Android Snackbar notifications",
|
||||
"functions": [
|
||||
{
|
||||
"name": "show",
|
||||
"type": "function",
|
||||
"description": "Shows a Snackbar with the given message and button",
|
||||
"parameters": [{
|
||||
"name": "message",
|
||||
"type": "string"
|
||||
},{
|
||||
"name": "button",
|
||||
"type": "string"
|
||||
}],
|
||||
"async": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,327 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global ChromeUtils, ExtensionAPI, ExtensionCommon, XPCOMUtils */
|
||||
|
||||
var Services;
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"EventDispatcher",
|
||||
"resource://gre/modules/Messaging.jsm"
|
||||
);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(
|
||||
this,
|
||||
"GlobalEventDispatcher",
|
||||
() => EventDispatcher.instance
|
||||
);
|
||||
|
||||
function getInfoFrameScript(messageName) {
|
||||
/* eslint-env mozilla/frame-script */
|
||||
|
||||
({ Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"));
|
||||
const PREVIEW_MAX_ITEMS = 10;
|
||||
const LOG_LEVEL_MAP = {
|
||||
0: "debug",
|
||||
1: "info",
|
||||
2: "warn",
|
||||
3: "error",
|
||||
};
|
||||
|
||||
function getInnerWindowId(window) {
|
||||
return window.windowUtils.currentInnerWindowID;
|
||||
}
|
||||
|
||||
function getInnerWindowIDsForAllFrames(window) {
|
||||
const innerWindowID = getInnerWindowId(window);
|
||||
let ids = [innerWindowID];
|
||||
|
||||
if (window.frames) {
|
||||
for (let i = 0; i < window.frames.length; i++) {
|
||||
ids = ids.concat(getInnerWindowIDsForAllFrames(window.frames[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
function getLoggedMessages(window, includePrivate = false) {
|
||||
const ids = getInnerWindowIDsForAllFrames(window);
|
||||
return getConsoleMessages(ids)
|
||||
.concat(getScriptErrors(ids, includePrivate))
|
||||
.sort((a, b) => a.timeStamp - b.timeStamp)
|
||||
.map(m => m.message);
|
||||
}
|
||||
|
||||
function getPreview(value) {
|
||||
switch (typeof value) {
|
||||
case "function":
|
||||
return "function ()";
|
||||
|
||||
case "object":
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return `(${value.length})[...]`;
|
||||
}
|
||||
|
||||
return "{...}";
|
||||
|
||||
case "undefined":
|
||||
return "undefined";
|
||||
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function getArrayPreview(arr) {
|
||||
const preview = [];
|
||||
for (const value of arr) {
|
||||
preview.push(getPreview(value));
|
||||
|
||||
if (preview.length === PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return preview;
|
||||
}
|
||||
|
||||
function getObjectPreview(obj) {
|
||||
const preview = {};
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
preview[key] = getPreview(obj[key]);
|
||||
}
|
||||
|
||||
if (Object.keys(preview).length === PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return preview;
|
||||
}
|
||||
|
||||
function getArgs(value) {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (Array.isArray(value)) {
|
||||
return getArrayPreview(value);
|
||||
}
|
||||
|
||||
return getObjectPreview(value);
|
||||
}
|
||||
|
||||
return getPreview(value);
|
||||
}
|
||||
|
||||
function getConsoleMessages(windowIds) {
|
||||
const ConsoleAPIStorage = Cc[
|
||||
"@mozilla.org/consoleAPI-storage;1"
|
||||
].getService(Ci.nsIConsoleAPIStorage);
|
||||
let messages = [];
|
||||
for (const id of windowIds) {
|
||||
messages = messages.concat(ConsoleAPIStorage.getEvents(id) || []);
|
||||
}
|
||||
return messages.map(evt => {
|
||||
const { columnNumber, filename, level, lineNumber, timeStamp } = evt;
|
||||
const args = evt.arguments.map(getArgs);
|
||||
|
||||
const message = {
|
||||
level,
|
||||
log: args,
|
||||
uri: filename,
|
||||
pos: `${lineNumber}:${columnNumber}`,
|
||||
};
|
||||
|
||||
return { timeStamp, message };
|
||||
});
|
||||
}
|
||||
|
||||
function getScriptErrors(windowIds, includePrivate = false) {
|
||||
const messages = Services.console.getMessageArray() || [];
|
||||
return messages
|
||||
.filter(message => {
|
||||
if (message instanceof Ci.nsIScriptError) {
|
||||
if (!includePrivate && message.isFromPrivateWindow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (windowIds && !windowIds.includes(message.innerWindowID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If this is not an nsIScriptError and we need to do window-based
|
||||
// filtering we skip this message.
|
||||
return false;
|
||||
})
|
||||
.map(error => {
|
||||
const {
|
||||
timeStamp,
|
||||
errorMessage,
|
||||
sourceName,
|
||||
lineNumber,
|
||||
columnNumber,
|
||||
logLevel,
|
||||
} = error;
|
||||
const message = {
|
||||
level: LOG_LEVEL_MAP[logLevel],
|
||||
log: [errorMessage],
|
||||
uri: sourceName,
|
||||
pos: `${lineNumber}:${columnNumber}`,
|
||||
};
|
||||
|
||||
return { timeStamp, message };
|
||||
});
|
||||
}
|
||||
|
||||
sendAsyncMessage(messageName, {
|
||||
hasMixedActiveContentBlocked: docShell.hasMixedActiveContentBlocked,
|
||||
hasMixedDisplayContentBlocked: docShell.hasMixedDisplayContentBlocked,
|
||||
hasTrackingContentBlocked: docShell.hasTrackingContentBlocked,
|
||||
log: getLoggedMessages(content, true), // also on private tabs
|
||||
});
|
||||
}
|
||||
|
||||
this.tabExtras = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
const EventManager = ExtensionCommon.EventManager;
|
||||
const { tabManager } = context.extension;
|
||||
const {
|
||||
Management: {
|
||||
global: { windowTracker },
|
||||
},
|
||||
} = ChromeUtils.import("resource://gre/modules/Extension.jsm", null);
|
||||
return {
|
||||
tabExtras: {
|
||||
onDesktopSiteRequested: new EventManager({
|
||||
context,
|
||||
name: "tabExtras.onDesktopSiteRequested",
|
||||
register: fire => {
|
||||
const callback = tab => {
|
||||
fire.async(tab).catch(() => {}); // ignore Message Manager disconnects
|
||||
};
|
||||
const listener = {
|
||||
onEvent: (event, data, _callback) => {
|
||||
if (event === "DesktopMode:Change" && data.desktopMode) {
|
||||
callback(data.tabId);
|
||||
}
|
||||
},
|
||||
};
|
||||
GlobalEventDispatcher.registerListener(
|
||||
listener,
|
||||
"DesktopMode:Change"
|
||||
);
|
||||
return () => {
|
||||
GlobalEventDispatcher.unregisterListener(
|
||||
listener,
|
||||
"DesktopMode:Change"
|
||||
);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
async createPrivateTab() {
|
||||
const { BrowserApp } = windowTracker.topWindow;
|
||||
const nativeTab = BrowserApp.addTab("about:blank", {
|
||||
selected: true,
|
||||
isPrivate: true,
|
||||
});
|
||||
return Promise.resolve(tabManager.convert(nativeTab));
|
||||
},
|
||||
async loadURIWithPostData(
|
||||
tabId,
|
||||
url,
|
||||
postDataString,
|
||||
postDataContentType
|
||||
) {
|
||||
const tab = tabManager.get(tabId);
|
||||
if (!tab || !tab.browser) {
|
||||
return Promise.reject("Invalid tab");
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(url);
|
||||
} catch (_) {
|
||||
return Promise.reject("Invalid url");
|
||||
}
|
||||
|
||||
if (
|
||||
typeof postDataString !== "string" &&
|
||||
!(postDataString instanceof String)
|
||||
) {
|
||||
return Promise.reject("postDataString must be a string");
|
||||
}
|
||||
|
||||
const stringStream = Cc[
|
||||
"@mozilla.org/io/string-input-stream;1"
|
||||
].createInstance(Ci.nsIStringInputStream);
|
||||
stringStream.data = postData;
|
||||
const postData = Cc[
|
||||
"@mozilla.org/network/mime-input-stream;1"
|
||||
].createInstance(Ci.nsIMIMEInputStream);
|
||||
postData.addHeader(
|
||||
"Content-Type",
|
||||
postDataContentType || "application/x-www-form-urlencoded"
|
||||
);
|
||||
postData.setData(stringStream);
|
||||
|
||||
return new Promise(resolve => {
|
||||
const listener = {
|
||||
onLocationChange(
|
||||
browser,
|
||||
webProgress,
|
||||
request,
|
||||
locationURI,
|
||||
flags
|
||||
) {
|
||||
if (
|
||||
webProgress.isTopLevel &&
|
||||
browser === tab.browser &&
|
||||
locationURI.spec === url
|
||||
) {
|
||||
windowTracker.removeListener("progress", listener);
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
};
|
||||
windowTracker.addListener("progress", listener);
|
||||
let loadURIOptions = {
|
||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
postData,
|
||||
};
|
||||
tab.browser.webNavigation.loadURI(url, loadURIOptions);
|
||||
});
|
||||
},
|
||||
async getWebcompatInfo(tabId) {
|
||||
return new Promise(resolve => {
|
||||
const messageName = "WebExtension:GetWebcompatInfo";
|
||||
const code = `${getInfoFrameScript.toString()};getInfoFrameScript("${messageName}")`;
|
||||
const mm = tabManager.get(tabId).browser.messageManager;
|
||||
mm.loadFrameScript(`data:,${encodeURI(code)}`, false);
|
||||
mm.addMessageListener(messageName, function receiveFn(message) {
|
||||
mm.removeMessageListener(messageName, receiveFn);
|
||||
resolve(message.json);
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
@ -1,57 +0,0 @@
|
||||
[
|
||||
{
|
||||
"namespace": "tabExtras",
|
||||
"description": "experimental tab API extensions",
|
||||
"events": [{
|
||||
"name": "onDesktopSiteRequested",
|
||||
"type": "function",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tabId",
|
||||
"type": "integer",
|
||||
"description": "The related tab's id"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"functions": [
|
||||
{
|
||||
"name": "createPrivateTab",
|
||||
"type": "function",
|
||||
"description": "Create and select a new private about:blank tab",
|
||||
"parameters": [],
|
||||
"async": true
|
||||
},
|
||||
{
|
||||
"name": "getWebcompatInfo",
|
||||
"type": "function",
|
||||
"description": "Gets the content blocking status and script log for a given tab",
|
||||
"parameters": [{
|
||||
"type": "integer",
|
||||
"name": "tabId",
|
||||
"minimum": 0
|
||||
}],
|
||||
"async": true
|
||||
},
|
||||
{
|
||||
"name": "loadURIWithPostData",
|
||||
"type": "function",
|
||||
"description": "Loads a URI on the given tab using a POST request",
|
||||
"parameters": [{
|
||||
"type": "integer",
|
||||
"name": "tabId",
|
||||
"minimum": 0
|
||||
}, {
|
||||
"type": "string",
|
||||
"name": "url"
|
||||
}, {
|
||||
"type": "string",
|
||||
"name": "postData"
|
||||
}, {
|
||||
"type": "string",
|
||||
"name": "postDataContentType"
|
||||
}],
|
||||
"async": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,72 +0,0 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "WebCompat Reporter",
|
||||
"description": "Report site compatibility issues on webcompat.com",
|
||||
"author": "Thomas Wisniewski <twisniewski@mozilla.com>",
|
||||
"version": "1.1.0",
|
||||
"homepage_url": "https://github.com/mozilla/webcompat-reporter",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "webcompat-reporter@mozilla.org"
|
||||
}
|
||||
},
|
||||
"experiment_apis": {
|
||||
"aboutConfigPrefs": {
|
||||
"schema": "experimentalAPIs/aboutConfigPrefs.json",
|
||||
"parent": {
|
||||
"scopes": ["addon_parent"],
|
||||
"script": "experimentalAPIs/aboutConfigPrefs.js",
|
||||
"paths": [["aboutConfigPrefs"]]
|
||||
}
|
||||
},
|
||||
"browserInfo": {
|
||||
"schema": "experimentalAPIs/browserInfo.json",
|
||||
"parent": {
|
||||
"scopes": ["addon_parent"],
|
||||
"script": "experimentalAPIs/browserInfo.js",
|
||||
"paths": [["browserInfo"]]
|
||||
}
|
||||
},
|
||||
"l10n": {
|
||||
"schema": "experimentalAPIs/l10n.json",
|
||||
"parent": {
|
||||
"scopes": ["addon_parent"],
|
||||
"script": "experimentalAPIs/l10n.js",
|
||||
"paths": [["l10n"]]
|
||||
}
|
||||
},
|
||||
"nativeMenu": {
|
||||
"schema": "experimentalAPIs/nativeMenu.json",
|
||||
"parent": {
|
||||
"scopes": ["addon_parent"],
|
||||
"script": "experimentalAPIs/nativeMenu.js",
|
||||
"paths": [["nativeMenu"]]
|
||||
}
|
||||
},
|
||||
"snackbars": {
|
||||
"schema": "experimentalAPIs/snackbars.json",
|
||||
"parent": {
|
||||
"scopes": ["addon_parent"],
|
||||
"script": "experimentalAPIs/snackbars.js",
|
||||
"paths": [["snackbars"]]
|
||||
}
|
||||
},
|
||||
"tabExtras": {
|
||||
"schema": "experimentalAPIs/tabExtras.json",
|
||||
"parent": {
|
||||
"scopes": ["addon_parent"],
|
||||
"script": "experimentalAPIs/tabExtras.js",
|
||||
"paths": [["tabExtras"]]
|
||||
}
|
||||
}
|
||||
},
|
||||
"permissions": [
|
||||
"tabs",
|
||||
"<all_urls>"
|
||||
],
|
||||
"background": {
|
||||
"scripts": [
|
||||
"background.js"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
||||
DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
|
||||
|
||||
FINAL_TARGET_FILES.features['webcompat-reporter@mozilla.org'] += [
|
||||
'background.js',
|
||||
'manifest.json'
|
||||
]
|
||||
|
||||
FINAL_TARGET_FILES.features['webcompat-reporter@mozilla.org'].experimentalAPIs += [
|
||||
'experimentalAPIs/aboutConfigPrefs.js',
|
||||
'experimentalAPIs/aboutConfigPrefs.json',
|
||||
'experimentalAPIs/browserInfo.js',
|
||||
'experimentalAPIs/browserInfo.json',
|
||||
'experimentalAPIs/l10n.js',
|
||||
'experimentalAPIs/l10n.json',
|
||||
'experimentalAPIs/nativeMenu.js',
|
||||
'experimentalAPIs/nativeMenu.json',
|
||||
'experimentalAPIs/snackbars.js',
|
||||
'experimentalAPIs/snackbars.json',
|
||||
'experimentalAPIs/tabExtras.js',
|
||||
'experimentalAPIs/tabExtras.json'
|
||||
]
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Web Compatibility Tools', 'General')
|
@ -1,37 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["AboutCompat"];
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const addonID = "webcompat@mozilla.org";
|
||||
const addonPageRelativeURL = "/about-compat/aboutCompat.html";
|
||||
|
||||
function AboutCompat() {
|
||||
this.chromeURL = WebExtensionPolicy.getByID(addonID).getURL(
|
||||
addonPageRelativeURL
|
||||
);
|
||||
}
|
||||
AboutCompat.prototype = {
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIAboutModule]),
|
||||
getURIFlags() {
|
||||
return Ci.nsIAboutModule.URI_MUST_LOAD_IN_EXTENSION_PROCESS;
|
||||
},
|
||||
|
||||
newChannel(aURI, aLoadInfo) {
|
||||
const uri = Services.io.newURI(this.chromeURL);
|
||||
const channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
|
||||
channel.originalURI = aURI;
|
||||
|
||||
channel.owner = (Services.scriptSecurityManager.createContentPrincipal ||
|
||||
Services.scriptSecurityManager.createCodebasePrincipal)(
|
||||
uri,
|
||||
aLoadInfo.originAttributes
|
||||
);
|
||||
return channel;
|
||||
},
|
||||
};
|
@ -1,198 +0,0 @@
|
||||
@media (any-pointer: fine) {
|
||||
:root {
|
||||
font-family: sans-serif;
|
||||
margin: 40px auto;
|
||||
min-width: 30em;
|
||||
max-width: 60em;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.table-title-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.wide-button {
|
||||
display: block;
|
||||
min-height: 32px;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.submitting {
|
||||
background-image: url(chrome://global/skin/icons/loading.png);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px;
|
||||
}
|
||||
|
||||
.submitting .submit-crash-button-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.failed-to-submit {
|
||||
color: #ca8695;
|
||||
}
|
||||
|
||||
a.button-as-link {
|
||||
-moz-appearance: none;
|
||||
min-height: 30px;
|
||||
color: var(--in-content-text-color) !important;
|
||||
border: 1px solid var(--in-content-box-border-color) !important;
|
||||
border-radius: 2px;
|
||||
background-color: var(--in-content-page-background);
|
||||
line-height: 30px;
|
||||
margin: 4px 8px;
|
||||
/* Ensure font-size isn't overridden by widget styling (e.g. in forms.css) */
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
a.button-as-link:hover {
|
||||
background-color: var(--in-content-box-background-hover) !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h2.lighter-font-weight {
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
html[dir="ltr"] th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
html[dir="rtl"] th {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
@media (any-pointer: coarse), (any-pointer: none) {
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
-moz-text-size-adjust: none;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
table,
|
||||
tr,
|
||||
p {
|
||||
display: block;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
table {
|
||||
border-top: 2px solid #0a84ff;
|
||||
margin-top: -2px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
display: none;
|
||||
}
|
||||
|
||||
tr {
|
||||
position: relative;
|
||||
border-bottom: 1px solid #d7d9db;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
font-size: 94%;
|
||||
}
|
||||
|
||||
.tab {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
padding: 1em;
|
||||
font-weight: bold;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
border: 1px solid #d7d9db;
|
||||
border-bottom: 0;
|
||||
margin-bottom: 2px;
|
||||
background: #f5f5f5;
|
||||
color: #363b40;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
border-bottom-color: #fff;
|
||||
background: #fff;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: calc(1em + 2px);
|
||||
}
|
||||
|
||||
.tab.active + table {
|
||||
display: block;
|
||||
}
|
||||
|
||||
td {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
td:dir(ltr) {
|
||||
padding-right: 6.5em;
|
||||
}
|
||||
td:dir(rtl) {
|
||||
padding-left: 6.5em;
|
||||
}
|
||||
|
||||
td[colspan="4"] {
|
||||
padding: 1em;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
td:not([colspan]):nth-child(1) {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
td:not([colspan]):nth-child(1) {
|
||||
padding-bottom: 0.25em;
|
||||
}
|
||||
|
||||
td:nth-child(3) {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
button {
|
||||
background: #e8e8e7;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 6em;
|
||||
border: 0;
|
||||
border-left: 1px solid #d7d9db;
|
||||
-moz-appearance: none;
|
||||
color: #000;
|
||||
}
|
||||
button:dir(ltr) {
|
||||
right: 0;
|
||||
}
|
||||
button:dir(rtl) {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<base/>
|
||||
|
||||
<!-- If you change this script tag you must update the hash in the extension's
|
||||
`content_security_policy` 'sha256-MmZkN2QaIHhfRWPZ8TVRjijTn5Ci1iEabtTEWrt9CCo=' -->
|
||||
<script>/* globals browser */ document.head.firstElementChild.href = browser.runtime.getURL("");</script>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="about-compat/aboutCompat.css" />
|
||||
<link rel="stylesheet" media="screen and (pointer:fine), projection" type="text/css"
|
||||
href="chrome://global/skin/in-content/common.css"/>
|
||||
<link rel="localization" href="toolkit/about/aboutCompat.ftl"/>
|
||||
<title data-l10n-id="text-title"></title>
|
||||
<script src="about-compat/aboutCompat.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h2 class="tab active" data-l10n-id="label-overrides"></h2>
|
||||
<table id="overrides">
|
||||
<col/>
|
||||
<col/>
|
||||
<col/>
|
||||
</table>
|
||||
<h2 class="tab" data-l10n-id="label-interventions"></h2>
|
||||
<table id="interventions">
|
||||
<col/>
|
||||
<col/>
|
||||
<col/>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
@ -1,171 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* globals browser */
|
||||
|
||||
let availablePatches;
|
||||
|
||||
const portToAddon = (function() {
|
||||
let port;
|
||||
|
||||
function connect() {
|
||||
port = browser.runtime.connect({ name: "AboutCompatTab" });
|
||||
port.onMessage.addListener(onMessageFromAddon);
|
||||
port.onDisconnect.addListener(e => {
|
||||
port = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
connect();
|
||||
|
||||
async function send(message) {
|
||||
if (port) {
|
||||
return port.postMessage(message);
|
||||
}
|
||||
return Promise.reject("background script port disconnected");
|
||||
}
|
||||
|
||||
return { send };
|
||||
})();
|
||||
|
||||
const $ = function(sel) {
|
||||
return document.querySelector(sel);
|
||||
};
|
||||
|
||||
const DOMContentLoadedPromise = new Promise(resolve => {
|
||||
document.addEventListener(
|
||||
"DOMContentLoaded",
|
||||
() => {
|
||||
resolve();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
|
||||
Promise.all([
|
||||
browser.runtime.sendMessage("getOverridesAndInterventions"),
|
||||
DOMContentLoadedPromise,
|
||||
]).then(([info]) => {
|
||||
document.body.addEventListener("click", async evt => {
|
||||
const ele = evt.target;
|
||||
if (ele.nodeName === "BUTTON") {
|
||||
const row = ele.closest("[data-id]");
|
||||
if (row) {
|
||||
evt.preventDefault();
|
||||
ele.disabled = true;
|
||||
const id = row.getAttribute("data-id");
|
||||
try {
|
||||
await browser.runtime.sendMessage({ command: "toggle", id });
|
||||
} catch (_) {
|
||||
ele.disabled = false;
|
||||
}
|
||||
}
|
||||
} else if (ele.classList.contains("tab")) {
|
||||
document.querySelectorAll(".tab").forEach(tab => {
|
||||
tab.classList.remove("active");
|
||||
});
|
||||
ele.classList.add("active");
|
||||
}
|
||||
});
|
||||
|
||||
availablePatches = info;
|
||||
redraw();
|
||||
});
|
||||
|
||||
function onMessageFromAddon(msg) {
|
||||
if ("interventionsChanged" in msg) {
|
||||
redrawTable($("#interventions"), msg.interventionsChanged);
|
||||
}
|
||||
|
||||
if ("overridesChanged" in msg) {
|
||||
redrawTable($("#overrides"), msg.overridesChanged);
|
||||
}
|
||||
|
||||
const id = msg.toggling || msg.toggled;
|
||||
const button = $(`[data-id="${id}"] button`);
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
const active = msg.active;
|
||||
document.l10n.setAttributes(
|
||||
button,
|
||||
active ? "label-disable" : "label-enable"
|
||||
);
|
||||
button.disabled = !!msg.toggling;
|
||||
}
|
||||
|
||||
function redraw() {
|
||||
if (!availablePatches) {
|
||||
return;
|
||||
}
|
||||
const { overrides, interventions } = availablePatches;
|
||||
const showHidden = location.hash === "#all";
|
||||
redrawTable($("#overrides"), overrides, showHidden);
|
||||
redrawTable($("#interventions"), interventions, showHidden);
|
||||
}
|
||||
|
||||
function redrawTable(table, data, showHidden = false) {
|
||||
const df = document.createDocumentFragment();
|
||||
table.querySelectorAll("tr").forEach(tr => {
|
||||
tr.remove();
|
||||
});
|
||||
|
||||
let noEntriesMessage;
|
||||
if (data === false) {
|
||||
noEntriesMessage = "text-disabled-in-about-config";
|
||||
} else if (data.length === 0) {
|
||||
noEntriesMessage =
|
||||
table.id === "overrides" ? "text-no-overrides" : "text-no-interventions";
|
||||
}
|
||||
|
||||
if (noEntriesMessage) {
|
||||
const tr = document.createElement("tr");
|
||||
df.appendChild(tr);
|
||||
|
||||
const td = document.createElement("td");
|
||||
td.setAttribute("colspan", "3");
|
||||
document.l10n.setAttributes(td, noEntriesMessage);
|
||||
tr.appendChild(td);
|
||||
|
||||
table.appendChild(df);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const row of data) {
|
||||
if (row.hidden && !showHidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tr = document.createElement("tr");
|
||||
tr.setAttribute("data-id", row.id);
|
||||
df.appendChild(tr);
|
||||
|
||||
let td = document.createElement("td");
|
||||
td.innerText = row.domain;
|
||||
tr.appendChild(td);
|
||||
|
||||
td = document.createElement("td");
|
||||
const a = document.createElement("a");
|
||||
const bug = row.bug;
|
||||
a.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug}`;
|
||||
document.l10n.setAttributes(a, "label-more-information", { bug });
|
||||
a.target = "_blank";
|
||||
td.appendChild(a);
|
||||
tr.appendChild(td);
|
||||
|
||||
td = document.createElement("td");
|
||||
tr.appendChild(td);
|
||||
const button = document.createElement("button");
|
||||
document.l10n.setAttributes(
|
||||
button,
|
||||
row.active ? "label-disable" : "label-enable"
|
||||
);
|
||||
td.appendChild(button);
|
||||
}
|
||||
table.appendChild(df);
|
||||
}
|
||||
|
||||
window.onhashchange = redraw;
|
@ -1,42 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global ExtensionAPI, Services, XPCOMUtils */
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Services",
|
||||
"resource://gre/modules/Services.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"resProto",
|
||||
"@mozilla.org/network/protocol;1?name=resource",
|
||||
"nsISubstitutingProtocolHandler"
|
||||
);
|
||||
|
||||
const ResourceSubstitution = "webcompat";
|
||||
const ProcessScriptURL = "resource://webcompat/aboutPageProcessScript.js";
|
||||
|
||||
this.aboutPage = class extends ExtensionAPI {
|
||||
onStartup() {
|
||||
const { rootURI } = this.extension;
|
||||
|
||||
resProto.setSubstitution(
|
||||
ResourceSubstitution,
|
||||
Services.io.newURI("about-compat/", null, rootURI)
|
||||
);
|
||||
|
||||
Services.ppmm.loadProcessScript(ProcessScriptURL, true);
|
||||
}
|
||||
|
||||
onShutdown() {
|
||||
resProto.setSubstitution(ResourceSubstitution, null);
|
||||
|
||||
Services.ppmm.removeDelayedProcessScript(ProcessScriptURL);
|
||||
}
|
||||
};
|
@ -1,4 +0,0 @@
|
||||
[{
|
||||
"namespace": "aboutCompat",
|
||||
"description": "Enables the about:compat page"
|
||||
}]
|
@ -1,29 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
|
||||
const classID = Components.ID("{97bf9550-2a7b-11e9-b56e-0800200c9a66}");
|
||||
|
||||
if (!Cm.isCIDRegistered(classID)) {
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
const factory = XPCOMUtils.generateSingletonFactory(function() {
|
||||
const { AboutCompat } = ChromeUtils.import(
|
||||
"resource://webcompat/AboutCompat.jsm"
|
||||
);
|
||||
return new AboutCompat();
|
||||
});
|
||||
|
||||
Cm.registerFactory(
|
||||
classID,
|
||||
"about:compat",
|
||||
"@mozilla.org/network/protocol/about;1?what=compat",
|
||||
factory
|
||||
);
|
||||
}
|
@ -1,426 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* globals module */
|
||||
|
||||
/**
|
||||
* For detailed information on our policies, and a documention on this format
|
||||
* and its possibilites, please check the Mozilla-Wiki at
|
||||
*
|
||||
* https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
|
||||
*/
|
||||
const AVAILABLE_INJECTIONS = [
|
||||
{
|
||||
id: "testbed-injection",
|
||||
platform: "all",
|
||||
domain: "webcompat-addon-testbed.herokuapp.com",
|
||||
bug: "0000000",
|
||||
hidden: true,
|
||||
contentScripts: {
|
||||
matches: ["*://webcompat-addon-testbed.herokuapp.com/*"],
|
||||
css: [
|
||||
{
|
||||
file: "injections/css/bug0000000-testbed-css-injection.css",
|
||||
},
|
||||
],
|
||||
js: [
|
||||
{
|
||||
file: "injections/js/bug0000000-testbed-js-injection.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1452707",
|
||||
platform: "desktop",
|
||||
domain: "ib.absa.co.za",
|
||||
bug: "1452707",
|
||||
contentScripts: {
|
||||
matches: ["https://ib.absa.co.za/*"],
|
||||
js: [
|
||||
{
|
||||
file:
|
||||
"injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1457335",
|
||||
platform: "desktop",
|
||||
domain: "histography.io",
|
||||
bug: "1457335",
|
||||
contentScripts: {
|
||||
matches: ["*://histography.io/*"],
|
||||
js: [
|
||||
{
|
||||
file: "injections/js/bug1457335-histography.io-ua-change.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1472075",
|
||||
platform: "desktop",
|
||||
domain: "bankofamerica.com",
|
||||
bug: "1472075",
|
||||
contentScripts: {
|
||||
matches: ["*://*.bankofamerica.com/*"],
|
||||
js: [
|
||||
{
|
||||
file: "injections/js/bug1472075-bankofamerica.com-ua-change.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1472081",
|
||||
platform: "desktop",
|
||||
domain: "election.gov.np",
|
||||
bug: "1472081",
|
||||
contentScripts: {
|
||||
matches: ["http://202.166.205.141/bbvrs/*"],
|
||||
allFrames: true,
|
||||
js: [
|
||||
{
|
||||
file:
|
||||
"injections/js/bug1472081-election.gov.np-window.sidebar-shim.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1482066",
|
||||
platform: "desktop",
|
||||
domain: "portalminasnet.com",
|
||||
bug: "1482066",
|
||||
contentScripts: {
|
||||
matches: ["*://portalminasnet.com/*"],
|
||||
allFrames: true,
|
||||
js: [
|
||||
{
|
||||
file:
|
||||
"injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1570856",
|
||||
platform: "android",
|
||||
domain: "medium.com",
|
||||
bug: "1570856",
|
||||
contentScripts: {
|
||||
matches: ["*://medium.com/*"],
|
||||
js: [
|
||||
{
|
||||
file: "injections/js/bug1570856-medium.com-menu-isTier1.js",
|
||||
},
|
||||
],
|
||||
allFrames: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1579159",
|
||||
platform: "android",
|
||||
domain: "m.tailieu.vn",
|
||||
bug: "1579159",
|
||||
contentScripts: {
|
||||
matches: ["*://m.tailieu.vn/*", "*://m.elib.vn/*"],
|
||||
js: [
|
||||
{
|
||||
file: "injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js",
|
||||
},
|
||||
],
|
||||
allFrames: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1577245",
|
||||
platform: "android",
|
||||
domain: "help.pandora.com",
|
||||
bug: "1577245",
|
||||
contentScripts: {
|
||||
matches: [
|
||||
"https://faq.usps.com/*",
|
||||
"https://help.duo.com/*",
|
||||
"https://help.hulu.com/*",
|
||||
"https://help.pandora.com/*",
|
||||
"https://my211.force.com/*",
|
||||
"https://support.paypay.ne.jp/*",
|
||||
"https://usps.force.com/*",
|
||||
],
|
||||
js: [
|
||||
{
|
||||
file:
|
||||
"injections/js/bug1577245-salesforce-communities-hide-unsupported.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1526977",
|
||||
platform: "desktop",
|
||||
domain: "sreedharscce.in",
|
||||
bug: "1526977",
|
||||
contentScripts: {
|
||||
matches: ["*://*.sreedharscce.in/authenticate"],
|
||||
css: [
|
||||
{
|
||||
file: "injections/css/bug1526977-sreedharscce.in-login-fix.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1518781",
|
||||
platform: "desktop",
|
||||
domain: "twitch.tv",
|
||||
bug: "1518781",
|
||||
contentScripts: {
|
||||
matches: ["*://*.twitch.tv/*"],
|
||||
css: [
|
||||
{
|
||||
file: "injections/css/bug1518781-twitch.tv-webkit-scrollbar.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1551672",
|
||||
platform: "android",
|
||||
domain: "Sites using PDK 5 video",
|
||||
bug: "1551672",
|
||||
data: {
|
||||
urls: ["https://*/*/tpPdk.js", "https://*/*/pdk/js/*/*.js"],
|
||||
types: ["script"],
|
||||
},
|
||||
customFunc: "pdk5fix",
|
||||
},
|
||||
{
|
||||
id: "bug1577870",
|
||||
platform: "desktop",
|
||||
domain: "Download prompt for files with no content-type",
|
||||
bug: "1577870",
|
||||
data: {
|
||||
urls: [
|
||||
"https://*.linkedin.com/tscp-serving/dtag*",
|
||||
"https://ads-us.rd.linksynergy.com/as.php*",
|
||||
"https://www.office.com/logout?sid*",
|
||||
],
|
||||
contentType: {
|
||||
name: "content-type",
|
||||
value: "text/html; charset=utf-8",
|
||||
},
|
||||
},
|
||||
customFunc: "noSniffFix",
|
||||
},
|
||||
{
|
||||
id: "bug1305028",
|
||||
platform: "desktop",
|
||||
domain: "gaming.youtube.com",
|
||||
bug: "1305028",
|
||||
contentScripts: {
|
||||
matches: ["*://gaming.youtube.com/*"],
|
||||
css: [
|
||||
{
|
||||
file:
|
||||
"injections/css/bug1305028-gaming.youtube.com-webkit-scrollbar.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1432935-discord",
|
||||
platform: "desktop",
|
||||
domain: "discordapp.com",
|
||||
bug: "1432935",
|
||||
contentScripts: {
|
||||
matches: ["*://discordapp.com/*"],
|
||||
css: [
|
||||
{
|
||||
file:
|
||||
"injections/css/bug1432935-discordapp.com-webkit-scorllbar-white-line.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1561371",
|
||||
platform: "android",
|
||||
domain: "mail.google.com",
|
||||
bug: "1561371",
|
||||
contentScripts: {
|
||||
matches: ["*://mail.google.com/*"],
|
||||
css: [
|
||||
{
|
||||
file:
|
||||
"injections/css/bug1561371-mail.google.com-allow-horizontal-scrolling.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1567610",
|
||||
platform: "all",
|
||||
domain: "dns.google.com",
|
||||
bug: "1567610",
|
||||
contentScripts: {
|
||||
matches: ["*://dns.google.com/*", "*://dns.google/*"],
|
||||
css: [
|
||||
{
|
||||
file: "injections/css/bug1567610-dns.google.com-moz-fit-content.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1568256",
|
||||
platform: "android",
|
||||
domain: "zertifikate.commerzbank.de",
|
||||
bug: "1568256",
|
||||
contentScripts: {
|
||||
matches: ["*://*.zertifikate.commerzbank.de/webforms/mobile/*"],
|
||||
css: [
|
||||
{
|
||||
file: "injections/css/bug1568256-zertifikate.commerzbank.de-flex.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1568908",
|
||||
platform: "desktop",
|
||||
domain: "console.cloud.google.com",
|
||||
bug: "1568908",
|
||||
contentScripts: {
|
||||
matches: ["*://*.console.cloud.google.com/*"],
|
||||
css: [
|
||||
{
|
||||
file:
|
||||
"injections/css/bug1568908-console.cloud.google.com-scrollbar-fix.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1570119",
|
||||
platform: "desktop",
|
||||
domain: "teamcoco.com",
|
||||
bug: "1570119",
|
||||
contentScripts: {
|
||||
matches: ["*://teamcoco.com/*"],
|
||||
css: [
|
||||
{
|
||||
file: "injections/css/bug1570119-teamcoco.com-scrollbar-width.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1570328",
|
||||
platform: "android",
|
||||
domain: "developer.apple.com",
|
||||
bug: "1570328",
|
||||
contentScripts: {
|
||||
matches: ["*://developer.apple.com/*"],
|
||||
css: [
|
||||
{
|
||||
file:
|
||||
"injections/css/bug1570328-developer-apple.com-transform-scale.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1574973",
|
||||
platform: "android",
|
||||
domain: "patch.com",
|
||||
bug: "1574973",
|
||||
contentScripts: {
|
||||
matches: ["*://patch.com/*"],
|
||||
css: [
|
||||
{
|
||||
file: "injections/css/bug1574973-patch.com-dropdown-menu-fix.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1575000",
|
||||
platform: "all",
|
||||
domain: "apply.lloydsbank.co.uk",
|
||||
bug: "1575000",
|
||||
contentScripts: {
|
||||
matches: ["*://apply.lloydsbank.co.uk/*"],
|
||||
css: [
|
||||
{
|
||||
file:
|
||||
"injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1575011",
|
||||
platform: "android",
|
||||
domain: "holiday-weather.com",
|
||||
bug: "1575011",
|
||||
contentScripts: {
|
||||
matches: ["*://*.holiday-weather.com/*"],
|
||||
css: [
|
||||
{
|
||||
file:
|
||||
"injections/css/bug1575011-holiday-weather.com-scrolling-fix.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1575017",
|
||||
platform: "desktop",
|
||||
domain: "dunkindonuts.com",
|
||||
bug: "1575017",
|
||||
contentScripts: {
|
||||
matches: ["*://*.dunkindonuts.com/en/sign-in*"],
|
||||
css: [
|
||||
{
|
||||
file: "injections/css/bug1575017-dunkindonuts.com-flex-basis.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1577270",
|
||||
platform: "android",
|
||||
domain: "binance.com",
|
||||
bug: "1577270",
|
||||
contentScripts: {
|
||||
matches: ["*://*.binance.com/*"],
|
||||
css: [
|
||||
{
|
||||
file: "injections/css/bug1577270-binance.com-calc-height-fix.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1577297",
|
||||
platform: "android",
|
||||
domain: "kitkat.com.au",
|
||||
bug: "1577297",
|
||||
contentScripts: {
|
||||
matches: ["*://*.kitkat.com.au/*"],
|
||||
css: [
|
||||
{
|
||||
file: "injections/css/bug1577297-kitkat.com.au-slider-width-fix.css",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = AVAILABLE_INJECTIONS;
|
@ -1,580 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* globals module, require */
|
||||
|
||||
// This is a hack for the tests.
|
||||
if (typeof getMatchPatternsForGoogleURL === "undefined") {
|
||||
var getMatchPatternsForGoogleURL = require("../lib/google");
|
||||
}
|
||||
|
||||
/**
|
||||
* For detailed information on our policies, and a documention on this format
|
||||
* and its possibilites, please check the Mozilla-Wiki at
|
||||
*
|
||||
* https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
|
||||
*/
|
||||
const AVAILABLE_UA_OVERRIDES = [
|
||||
{
|
||||
id: "testbed-override",
|
||||
platform: "all",
|
||||
domain: "webcompat-addon-testbed.herokuapp.com",
|
||||
bug: "0000000",
|
||||
config: {
|
||||
hidden: true,
|
||||
matches: ["*://webcompat-addon-testbed.herokuapp.com/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36 for WebCompat"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1564594 - Create UA override for Enhanced Search on Firefox Android
|
||||
*
|
||||
* Enables the Chrome Google Search experience for Fennec users.
|
||||
*/
|
||||
id: "bug1564594",
|
||||
platform: "android",
|
||||
domain: "Enhanced Search",
|
||||
bug: "1567945",
|
||||
config: {
|
||||
matches: [
|
||||
...getMatchPatternsForGoogleURL("images.google"),
|
||||
...getMatchPatternsForGoogleURL("maps.google"),
|
||||
...getMatchPatternsForGoogleURL("news.google"),
|
||||
...getMatchPatternsForGoogleURL("www.google"),
|
||||
],
|
||||
blocks: [...getMatchPatternsForGoogleURL("www.google", "serviceworker")],
|
||||
permanentPref: "enable_enhanced_search",
|
||||
telemetryKey: "enhancedSearch",
|
||||
experiment: ["enhanced-search", "enhanced-search-control"],
|
||||
uaTransformer: originalUA => {
|
||||
return UAHelpers.getDeviceAppropriateChromeUA();
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1563839 - rolb.santanderbank.com - Build UA override
|
||||
* WebCompat issue #33462 - https://webcompat.com/issues/33462
|
||||
*
|
||||
* santanderbank expects UA to have 'like Gecko', otherwise it runs
|
||||
* xmlDoc.onload whose support has been dropped. It results in missing labels in forms
|
||||
* and some other issues. Adding 'like Gecko' fixes those issues.
|
||||
*/
|
||||
id: "bug1563839",
|
||||
platform: "all",
|
||||
domain: "rolb.santanderbank.com",
|
||||
bug: "1563839",
|
||||
config: {
|
||||
matches: [
|
||||
"*://*.santander.co.uk/*",
|
||||
"*://bob.santanderbank.com/*",
|
||||
"*://rolb.santanderbank.com/*",
|
||||
],
|
||||
uaTransformer: originalUA => {
|
||||
return originalUA.replace("Gecko", "like Gecko");
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1577179 - UA override for supportforms.embarcadero.com
|
||||
* WebCompat issue #34682 - https://webcompat.com/issues/34682
|
||||
*
|
||||
* supportforms.embarcadero.com has a constant onchange event on a product selector
|
||||
* which makes it unusable. Spoofing as Chrome allows to stop event from firing
|
||||
*/
|
||||
id: "bug1577179",
|
||||
platform: "all",
|
||||
domain: "supportforms.embarcadero.com",
|
||||
bug: "1577179",
|
||||
config: {
|
||||
matches: ["*://supportforms.embarcadero.com/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1577519 - att.tv - Create a UA override for att.tv for playback on desktop
|
||||
* WebCompat issue #3846 - https://webcompat.com/issues/3846
|
||||
*
|
||||
* att.tv (atttvnow.com) is blocking Firefox via UA sniffing. Spoofing as Chrome allows
|
||||
* to access the site and playback works fine. This is former directvnow.com
|
||||
*/
|
||||
id: "bug1577519",
|
||||
platform: "desktop",
|
||||
domain: "att.tv",
|
||||
bug: "1577519",
|
||||
config: {
|
||||
matches: ["*://*.att.tv/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1570108 - steamcommunity.com - UA override for steamcommunity.com
|
||||
* WebCompat issue #34171 - https://webcompat.com/issues/34171
|
||||
*
|
||||
* steamcommunity.com blocks chat feature for Firefox users showing unsupported browser message.
|
||||
* When spoofing as Chrome the chat works fine
|
||||
*/
|
||||
id: "bug1570108",
|
||||
platform: "desktop",
|
||||
domain: "steamcommunity.com",
|
||||
bug: "1570108",
|
||||
config: {
|
||||
matches: ["*://steamcommunity.com/chat*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1582582 - sling.com - UA override for sling.com
|
||||
* WebCompat issue #17804 - https://webcompat.com/issues/17804
|
||||
*
|
||||
* sling.com blocks Firefox users showing unsupported browser message.
|
||||
* When spoofing as Chrome playing content works fine
|
||||
*/
|
||||
id: "bug1582582",
|
||||
platform: "desktop",
|
||||
domain: "sling.com",
|
||||
bug: "1582582",
|
||||
config: {
|
||||
matches: ["https://watch.sling.com/*", "https://www.sling.com/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1480710 - m.imgur.com - Build UA override
|
||||
* WebCompat issue #13154 - https://webcompat.com/issues/13154
|
||||
*
|
||||
* imgur returns a 404 for requests to CSS and JS file if requested with a Fennec
|
||||
* User Agent. By removing the Fennec identifies and adding Chrome Mobile's, we
|
||||
* receive the correct CSS and JS files.
|
||||
*/
|
||||
id: "bug1480710",
|
||||
platform: "android",
|
||||
domain: "m.imgur.com",
|
||||
bug: "1480710",
|
||||
config: {
|
||||
matches: ["*://m.imgur.com/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 945963 - tieba.baidu.com serves simplified mobile content to Firefox Android
|
||||
* WebCompat issue #18455 - https://webcompat.com/issues/18455
|
||||
*
|
||||
* tieba.baidu.com and tiebac.baidu.com serve a heavily simplified and less functional
|
||||
* mobile experience to Firefox for Android users. Adding the AppleWebKit indicator
|
||||
* to the User Agent gets us the same experience.
|
||||
*/
|
||||
id: "bug945963",
|
||||
platform: "android",
|
||||
domain: "tieba.baidu.com",
|
||||
bug: "945963",
|
||||
config: {
|
||||
matches: [
|
||||
"*://tieba.baidu.com/*",
|
||||
"*://tiebac.baidu.com/*",
|
||||
"*://zhidao.baidu.com/*",
|
||||
],
|
||||
uaTransformer: originalUA => {
|
||||
return originalUA + " AppleWebKit/537.36 (KHTML, like Gecko)";
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1177298 - Write UA overrides for top Japanese Sites
|
||||
* (Imported from ua-update.json.in)
|
||||
*
|
||||
* To receive the proper mobile version instead of the desktop version or
|
||||
* a lower grade mobile experience, the UA is spoofed.
|
||||
*/
|
||||
id: "bug1177298-2",
|
||||
platform: "android",
|
||||
domain: "lohaco.jp",
|
||||
bug: "1177298",
|
||||
config: {
|
||||
matches: ["*://*.lohaco.jp/*"],
|
||||
uaTransformer: _ => {
|
||||
return "Mozilla/5.0 (Linux; Android 5.0.2; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36";
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1177298 - Write UA overrides for top Japanese Sites
|
||||
* (Imported from ua-update.json.in)
|
||||
*
|
||||
* To receive the proper mobile version instead of the desktop version or
|
||||
* a lower grade mobile experience, the UA is spoofed.
|
||||
*/
|
||||
id: "bug1177298-3",
|
||||
platform: "android",
|
||||
domain: "nhk.or.jp",
|
||||
bug: "1177298",
|
||||
config: {
|
||||
matches: ["*://*.nhk.or.jp/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return originalUA + " AppleWebKit";
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1338260 - Add UA override for directTV
|
||||
* (Imported from ua-update.json.in)
|
||||
*
|
||||
* DirectTV has issues with scrolling and cut-off images. Pretending to be
|
||||
* Chrome for Android fixes those issues.
|
||||
*/
|
||||
id: "bug1338260",
|
||||
platform: "android",
|
||||
domain: "directv.com",
|
||||
bug: "1338260",
|
||||
config: {
|
||||
matches: ["*://*.directv.com/*"],
|
||||
uaTransformer: _ => {
|
||||
return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36";
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1385206 - Create UA override for rakuten.co.jp on Firefox Android
|
||||
* (Imported from ua-update.json.in)
|
||||
*
|
||||
* rakuten.co.jp serves a Desktop version if Firefox is included in the UA.
|
||||
*/
|
||||
id: "bug1385206",
|
||||
platform: "android",
|
||||
domain: "rakuten.co.jp",
|
||||
bug: "1385206",
|
||||
config: {
|
||||
matches: ["*://*.rakuten.co.jp/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return originalUA.replace(/Firefox.+$/, "");
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 969844 - mobile.de sends desktop site to Firefox on Android
|
||||
*
|
||||
* mobile.de sends the desktop site to Fennec. Spooing as Chrome works fine.
|
||||
*/
|
||||
id: "bug969844",
|
||||
platform: "android",
|
||||
domain: "mobile.de",
|
||||
bug: "969844",
|
||||
config: {
|
||||
matches: ["*://*.mobile.de/*"],
|
||||
uaTransformer: _ => {
|
||||
return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36";
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1509831 - cc.com - Add UA override for CC.com
|
||||
* WebCompat issue #329 - https://webcompat.com/issues/329
|
||||
*
|
||||
* ComedyCentral blocks Firefox for not being able to play HLS, which was
|
||||
* true in previous versions, but no longer is. With a spoofed Chrome UA,
|
||||
* the site works just fine.
|
||||
*/
|
||||
id: "bug1509831",
|
||||
platform: "android",
|
||||
domain: "cc.com",
|
||||
bug: "1509831",
|
||||
config: {
|
||||
matches: ["*://*.cc.com/*"],
|
||||
uaTransformer: _ => {
|
||||
return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36";
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1508516 - cineflix.com.br - Add UA override for cineflix.com.br/m/
|
||||
* WebCompat issue #21553 - https://webcompat.com/issues/21553
|
||||
*
|
||||
* The site renders a blank page with any Firefox snipped in the UA as it
|
||||
* is running into an exception. Spoofing as Chrome makes the site work
|
||||
* fine.
|
||||
*/
|
||||
id: "bug1508516",
|
||||
platform: "android",
|
||||
domain: "cineflix.com.br",
|
||||
bug: "1508516",
|
||||
config: {
|
||||
matches: ["*://*.cineflix.com.br/m/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1509852 - redbull.com - Add UA override for redbull.com
|
||||
* WebCompat issue #21439 - https://webcompat.com/issues/21439
|
||||
*
|
||||
* Redbull.com blocks some features, for example the live video player, for
|
||||
* Fennec. Spoofing as Chrome results in us rendering the video just fine,
|
||||
* and everything else works as well.
|
||||
*/
|
||||
id: "bug1509852",
|
||||
platform: "android",
|
||||
domain: "redbull.com",
|
||||
bug: "1509852",
|
||||
config: {
|
||||
matches: ["*://*.redbull.com/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1509873 - zmags.com - Add UA override for secure.viewer.zmags.com
|
||||
* WebCompat issue #21576 - https://webcompat.com/issues/21576
|
||||
*
|
||||
* The zmags viewer locks out Fennec with a "Browser unsupported" message,
|
||||
* but tests showed that it works just fine with a Chrome UA. Outreach
|
||||
* attempts were unsuccessful, and as the site has a relatively high rank,
|
||||
* we alter the UA.
|
||||
*/
|
||||
id: "bug1509873",
|
||||
platform: "android",
|
||||
domain: "zmags.com",
|
||||
bug: "1509873",
|
||||
config: {
|
||||
matches: ["*://*.viewer.zmags.com/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1566253 - posts.google.com - Add UA override for posts.google.com
|
||||
* WebCompat issue #17870 - https://webcompat.com/issues/17870
|
||||
*
|
||||
* posts.google.com displaying "Your browser doesn't support this page".
|
||||
* Spoofing as Chrome works fine.
|
||||
*/
|
||||
id: "bug1566253",
|
||||
platform: "android",
|
||||
domain: "posts.google.com",
|
||||
bug: "1566253",
|
||||
config: {
|
||||
matches: ["*://posts.google.com/*"],
|
||||
uaTransformer: _ => {
|
||||
return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G900M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.101 Mobile Safari/537.36";
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1567945 - Create UA override for beeg.com on Firefox Android
|
||||
* WebCompat issue #16648 - https://webcompat.com/issues/16648
|
||||
*
|
||||
* beeg.com is hiding content of a page with video if Firefox exists in UA,
|
||||
* replacing "Firefox" with an empty string makes the page load
|
||||
*/
|
||||
id: "bug1567945",
|
||||
platform: "android",
|
||||
domain: "beeg.com",
|
||||
bug: "1567945",
|
||||
config: {
|
||||
matches: ["*://beeg.com/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return originalUA.replace(/Firefox.+$/, "");
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1574522 - UA override for enuri.com on Firefox for Android
|
||||
* WebCompat issue #37139 - https://webcompat.com/issues/37139
|
||||
*
|
||||
* enuri.com returns a different template for Firefox on Android
|
||||
* based on server side UA detection. This results in page content cut offs.
|
||||
* Spoofing as Chrome fixes the issue
|
||||
*/
|
||||
id: "bug1574522",
|
||||
platform: "android",
|
||||
domain: "enuri.com",
|
||||
bug: "1574522",
|
||||
config: {
|
||||
matches: ["*://enuri.com/*"],
|
||||
uaTransformer: _ => {
|
||||
return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G900M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36";
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1574564 - UA override for ceskatelevize.cz on Firefox for Android
|
||||
* WebCompat issue #15467 - https://webcompat.com/issues/15467
|
||||
*
|
||||
* ceskatelevize sets streamingProtocol depending on the User-Agent it sees
|
||||
* in the request headers, returning DASH for Chrome, HLS for iOS,
|
||||
* and Flash for Fennec. Since Fennec has no Flash, the video doesn't work.
|
||||
* Spoofing as Chrome makes the video play
|
||||
*/
|
||||
id: "bug1574564",
|
||||
platform: "android",
|
||||
domain: "ceskatelevize.cz",
|
||||
bug: "1574564",
|
||||
config: {
|
||||
matches: ["*://*.ceskatelevize.cz/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1577240 - UA override for heb.com on Firefox for Android
|
||||
* WebCompat issue #33613 - https://webcompat.com/issues/33613
|
||||
*
|
||||
* heb.com shows desktop site on Firefox for Android for some pages based on
|
||||
* UA detection. Spoofing as Chrome allows to get mobile site.
|
||||
*/
|
||||
id: "bug1577240",
|
||||
platform: "android",
|
||||
domain: "heb.com",
|
||||
bug: "1577240",
|
||||
config: {
|
||||
matches: ["*://*.heb.com/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1577250 - UA override for homebook.pl on Firefox for Android
|
||||
* WebCompat issue #24044 - https://webcompat.com/issues/24044
|
||||
*
|
||||
* homebook.pl shows desktop site on Firefox for Android based on
|
||||
* UA detection. Spoofing as Chrome allows to get mobile site.
|
||||
*/
|
||||
id: "bug1577250",
|
||||
platform: "android",
|
||||
domain: "homebook.pl",
|
||||
bug: "1577250",
|
||||
config: {
|
||||
matches: ["*://*.homebook.pl/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Bug 1577267 - UA override for metfone.com.kh on Firefox for Android
|
||||
* WebCompat issue #16363 - https://webcompat.com/issues/16363
|
||||
*
|
||||
* metfone.com.kh has a server side UA detection which returns desktop site
|
||||
* for Firefox for Android. Spoofing as Chrome allows to receive mobile version
|
||||
*/
|
||||
id: "bug1577267",
|
||||
platform: "android",
|
||||
domain: "metfone.com.kh",
|
||||
bug: "1577267",
|
||||
config: {
|
||||
matches: ["*://*.metfone.com.kh/*"],
|
||||
uaTransformer: originalUA => {
|
||||
return (
|
||||
UAHelpers.getPrefix(originalUA) +
|
||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const UAHelpers = {
|
||||
getDeviceAppropriateChromeUA() {
|
||||
if (!UAHelpers._deviceAppropriateChromeUA) {
|
||||
const userAgent =
|
||||
typeof navigator !== "undefined" ? navigator.userAgent : "";
|
||||
const RunningFirefoxVersion = (userAgent.match(/Firefox\/([0-9.]+)/) || [
|
||||
"",
|
||||
"58.0",
|
||||
])[1];
|
||||
const RunningAndroidVersion =
|
||||
userAgent.match(/Android\/[0-9.]+/) || "Android 6.0";
|
||||
const ChromeVersionToMimic = "76.0.3809.111";
|
||||
const ChromePhoneUA = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; Nexus 5 Build/MRA58N) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ChromeVersionToMimic} Mobile Safari/537.36`;
|
||||
const ChromeTabletUA = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; Nexus 7 Build/JSS15Q) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ChromeVersionToMimic} Safari/537.36`;
|
||||
const IsPhone = userAgent.includes("Mobile");
|
||||
UAHelpers._deviceAppropriateChromeUA = IsPhone
|
||||
? ChromePhoneUA
|
||||
: ChromeTabletUA;
|
||||
}
|
||||
return UAHelpers._deviceAppropriateChromeUA;
|
||||
},
|
||||
getPrefix(originalUA) {
|
||||
return originalUA.substr(0, originalUA.indexOf(")") + 1);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = AVAILABLE_UA_OVERRIDES;
|
@ -1,50 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global ExtensionAPI, ExtensionCommon, Services, XPCOMUtils */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
});
|
||||
|
||||
this.aboutConfigPrefs = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
const EventManager = ExtensionCommon.EventManager;
|
||||
const extensionIDBase = context.extension.id.split("@")[0];
|
||||
const extensionPrefNameBase = `extensions.${extensionIDBase}.`;
|
||||
|
||||
return {
|
||||
aboutConfigPrefs: {
|
||||
onPrefChange: new EventManager({
|
||||
context,
|
||||
name: "aboutConfigPrefs.onUAOverridesPrefChange",
|
||||
register: (fire, name) => {
|
||||
const prefName = `${extensionPrefNameBase}${name}`;
|
||||
const callback = () => {
|
||||
fire.async(name).catch(() => {}); // ignore Message Manager disconnects
|
||||
};
|
||||
Services.prefs.addObserver(prefName, callback);
|
||||
return () => {
|
||||
Services.prefs.removeObserver(prefName, callback);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
async getPref(name) {
|
||||
try {
|
||||
return Services.prefs.getBoolPref(
|
||||
`${extensionPrefNameBase}${name}`
|
||||
);
|
||||
} catch (_) {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
async setPref(name, value) {
|
||||
Services.prefs.setBoolPref(`${extensionPrefNameBase}${name}`, value);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
@ -1,53 +0,0 @@
|
||||
[
|
||||
{
|
||||
"namespace": "aboutConfigPrefs",
|
||||
"description": "experimental API extension to allow access to about:config preferences",
|
||||
"events": [
|
||||
{
|
||||
"name": "onPrefChange",
|
||||
"type": "function",
|
||||
"parameters": [{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"description": "The preference which changed"
|
||||
}],
|
||||
"extraParameters": [{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"description": "The preference to monitor"
|
||||
}]
|
||||
}
|
||||
],
|
||||
"functions": [
|
||||
{
|
||||
"name": "getPref",
|
||||
"type": "function",
|
||||
"description": "Get a preference's value",
|
||||
"parameters": [{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"description": "The preference name"
|
||||
}],
|
||||
"async": true
|
||||
},
|
||||
{
|
||||
"name": "setPref",
|
||||
"type": "function",
|
||||
"description": "Set a preference's value",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"description": "The preference name"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "boolean",
|
||||
"description": "The new value"
|
||||
}
|
||||
],
|
||||
"async": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,34 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global ExtensionAPI, Services, XPCOMUtils */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
EventDispatcher: "resource://gre/modules/Messaging.jsm",
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
});
|
||||
|
||||
this.experiments = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
function promiseActiveExperiments() {
|
||||
return EventDispatcher.instance.sendRequestForResult({
|
||||
type: "Experiments:GetActive",
|
||||
});
|
||||
}
|
||||
return {
|
||||
experiments: {
|
||||
async isActive(name) {
|
||||
if (!Services.androidBridge || !Services.androidBridge.isFennec) {
|
||||
return undefined;
|
||||
}
|
||||
return promiseActiveExperiments().then(experiments => {
|
||||
return experiments.includes(name);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
@ -1,21 +0,0 @@
|
||||
[
|
||||
{
|
||||
"namespace": "experiments",
|
||||
"description": "experimental API extension to allow checking the status of Fennec experiments via Switchboard",
|
||||
"functions": [
|
||||
{
|
||||
"name": "isActive",
|
||||
"type": "function",
|
||||
"description": "Determine if a given experiment is active.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"description": "The experiment's name"
|
||||
}
|
||||
],
|
||||
"async": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,33 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global ExtensionAPI, Services, XPCOMUtils */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
SharedPreferences: "resource://gre/modules/SharedPreferences.jsm",
|
||||
});
|
||||
|
||||
this.sharedPreferences = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
return {
|
||||
sharedPreferences: {
|
||||
async setCharPref(name, value) {
|
||||
if (!Services.androidBridge || !Services.androidBridge.isFennec) {
|
||||
return;
|
||||
}
|
||||
SharedPreferences.forApp().setCharPref(name, value);
|
||||
},
|
||||
async setBoolPref(name, value) {
|
||||
if (!Services.androidBridge || !Services.androidBridge.isFennec) {
|
||||
return;
|
||||
}
|
||||
SharedPreferences.forApp().setBoolPref(name, value);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
@ -1,44 +0,0 @@
|
||||
[
|
||||
{
|
||||
"namespace": "sharedPreferences",
|
||||
"description": "experimental API extension to allow setting SharedPreferences on Fennec",
|
||||
"functions": [
|
||||
{
|
||||
"name": "setBoolPref",
|
||||
"type": "function",
|
||||
"description": "Set the value of a boolean Fennec SharedPreference",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"description": "The key name"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "boolean",
|
||||
"description": "The new value"
|
||||
}
|
||||
],
|
||||
"async": true
|
||||
},
|
||||
{
|
||||
"name": "setCharPref",
|
||||
"type": "function",
|
||||
"description": "Set the value of a string Fennec SharedPreference",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"description": "The key name"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "string",
|
||||
"description": "The new value"
|
||||
}
|
||||
],
|
||||
"async": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,3 +0,0 @@
|
||||
#css-injection.red {
|
||||
background-color: #0f0;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
/**
|
||||
* gaming.youtube.com - The vertical scrollbar displayed for the main pane is
|
||||
* partially overlapped by the video itself
|
||||
* Bug #1305028 - https://bugzilla.mozilla.org/show_bug.cgi?id=1305028
|
||||
*
|
||||
* The scrollbar in the main player area is overlapped by the player, making the
|
||||
* design look broken. In Chrome, YouTube is using ::-webkit-scrollbar to style
|
||||
* the bar to match their expectations, but this doesn't work in Firefox.
|
||||
* To make it look less broken, we hide the scrollbar for the main video pane
|
||||
* entirely.
|
||||
*/
|
||||
ytg-scroll-pane.ytg-watch-page {
|
||||
scrollbar-width: none;
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
/**
|
||||
* discordapp.com - -webkit-scrollbar dependency causes visible white line
|
||||
* Part of Bug #1432935 - https://bugzilla.mozilla.org/show_bug.cgi?id=1432935
|
||||
* WebCompat issue #7919 - https://webcompat.com/issues/7919
|
||||
*
|
||||
* Discord depends on -webkit-scrollbar for styling and hiding their scrollbars
|
||||
* in the UI. Previously, the scrollbars overlapped content permanently.
|
||||
* However, this issue seems to be addressed now, but a small white line
|
||||
* still remains. While Discord is working on this, let's get rid of these
|
||||
* lines.
|
||||
*/
|
||||
.themeGhostHairline-DBD-2d .pad-29zQak,
|
||||
.themeGhostHairlineChannels-3G0x9_ .pad-29zQak {
|
||||
width: 3px !important;
|
||||
left: -3px !important;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
/**
|
||||
* twitch.tv - Comment interaction button is overlayed by scrollbar
|
||||
* Bug #1518781 - https://bugzilla.mozilla.org/show_bug.cgi?id=1518781
|
||||
*
|
||||
* The interaction buttons in Twitch' chat are partly overlayed by the
|
||||
* scrollbar, which makes them hard to use. Twitch uses
|
||||
* ::-webkit-scrollbar to make the scrollbar thinner, which isn't working in
|
||||
* Firefox.
|
||||
* Given that even scrollbar-width: thin; is not enough (see Bugzilla), let's
|
||||
* remove it entirely.
|
||||
*/
|
||||
.video-chat__message-list-wrapper {
|
||||
scrollbar-width: none;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* sreedharscce.in - Fix login form with CSS intervention
|
||||
* Bug #1526977 - https://bugzilla.mozilla.org/show_bug.cgi?id=1526977
|
||||
* WebCompat issue #21505 - https://webcompat.com/issues/21505
|
||||
*
|
||||
* The login form is partly moved out of the screen on sreedharscce.in in
|
||||
* Firefox. Enforcing the body height to the full viewport fixes this issue,
|
||||
* as the login form itself is posititoned with `position: absolute;`.
|
||||
*/
|
||||
body {
|
||||
height: 100vh;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* mail.google.com - The HTML email view does not allow horizontal scrolling
|
||||
* on Fennec due to a missing CSS rule which is only served to Chrome.
|
||||
* Bug #1561371 - https://bugzilla.mozilla.org/show_bug.cgi?id=1561371
|
||||
*
|
||||
* HTML emails may sometimes contain content that does not wrap, yet the
|
||||
* CSS served to Fennec does not permit scrolling horizontally. To prevent
|
||||
* this UX frustration, we enable horizontal scrolling.
|
||||
*/
|
||||
body > #views {
|
||||
overflow: auto;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* dns.google.com - Page content is shifted to the left side of the page
|
||||
* Bug #1567610 - https://bugzilla.mozilla.org/show_bug.cgi?id=1567610
|
||||
* WebCompat issue #22494 - https://webcompat.com/issues/22494
|
||||
*
|
||||
* Affected element is styled with width:fit-content; which is not
|
||||
* supported by Firefox yet, see https://bugzilla.mozilla.org/show_bug.cgi?id=1495868
|
||||
* Adding -moz-fit-content fixes the issue
|
||||
*/
|
||||
main > .ng-star-inserted > .centered {
|
||||
width: -moz-fit-content;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* zertifikate.commerzbank.de - clickable elements on the page are collapsed
|
||||
* Bug #1568256 - https://bugzilla.mozilla.org/show_bug.cgi?id=1568256
|
||||
* WebCompat issue #9102 - https://webcompat.com/issues/9102
|
||||
*
|
||||
* Affected elements have display:-webkit-box and display:flex applied, however,
|
||||
* listed in wrong order, so display:-webkit-box is becoming the final say.
|
||||
* Adding display: flex for those elements fixes the issue
|
||||
*/
|
||||
.x-layout-box {
|
||||
display: flex !important;
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
/**
|
||||
* console.cloud.google.com - Double scrollbar visisible on long pages
|
||||
* Bug #1568908 - https://bugzilla.mozilla.org/show_bug.cgi?id=1568908
|
||||
* WebCompat issue #33164 - https://webcompat.com/issues/33164
|
||||
*
|
||||
* For pages that have contents heigher than the page's height, a secondary
|
||||
* scrollbar outside the scrollable content area is visible. This is caused
|
||||
* by a difference in Flexbox behavior, which is being addressed in
|
||||
* https://bugs.chromium.org/p/chromium/issues/detail?id=981134
|
||||
*
|
||||
* Until this fix hits release and Google has updated their UI properly,
|
||||
* this intervention addresses the differences.
|
||||
*/
|
||||
central-page-area {
|
||||
min-height: 0;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* teamcoco.com - a scrollbar at the top covering navigation menu
|
||||
* Bug #1570119 - https://bugzilla.mozilla.org/show_bug.cgi?id=1570119
|
||||
*
|
||||
* The scrollbar is covering navigation items making them unusable.
|
||||
* There are ::-webkit-scrollbar css rules already applied to the scrollbar,
|
||||
* hiding it in Chrome. Adding the scrollbar-width: none fixes the issue in Firefox.
|
||||
*/
|
||||
.css-bdnz85 {
|
||||
scrollbar-width: none;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
/**
|
||||
* developer.apple.com - content of the page is shifted to the left
|
||||
* Bug #1570328 - https://bugzilla.mozilla.org/show_bug.cgi?id=1570328
|
||||
* WebCompat issue #4070 - https://webcompat.com/issues/4070
|
||||
*
|
||||
* The site is relying on zoom property which is not supported by Mozilla,
|
||||
* see https://bugzilla.mozilla.org/show_bug.cgi?id=390936. Adding a combination
|
||||
* of transform: scale(1.4), transform-origin and width fixes the issue
|
||||
*/
|
||||
@media only screen and (min-device-width: 320px) and (max-device-width: 980px),
|
||||
(min-device-width: 1024px) and (max-device-width: 1024px) and (min-device-height: 1366px) and (max-device-height: 1366px) and (min-width: 320px) and (max-width: 980px) {
|
||||
#tocContainer {
|
||||
transform-origin: 0 0;
|
||||
transform: scale(1.4);
|
||||
width: 71.4%;
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* patch.com - sub-menu expands at the bottom of the page and overlaps other elements.
|
||||
* Bug #1574973 - https://bugzilla.mozilla.org/show_bug.cgi?id=1574973
|
||||
* WebCompat issue #25384 - https://webcompat.com/issues/25384
|
||||
*
|
||||
* patch.con has a top:100% style on the relatively-positioned element
|
||||
* with class="dropdown-menu", and Firefox is incorrectly honoring that
|
||||
* style (resolving it to something nonzero), whereas Chrome just treats it as "auto"
|
||||
* see https://bugzilla.mozilla.org/show_bug.cgi?id=1092007
|
||||
*/
|
||||
#patch-nav-secondary .dropdown-menu {
|
||||
top: auto;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* apply.lloydsbank.co.uk - radio buttons are misplaced
|
||||
* Bug #1575000 - https://bugzilla.mozilla.org/show_bug.cgi?id=1575000
|
||||
* WebCompat issue #34969 - https://webcompat.com/issues/34969
|
||||
*
|
||||
* Radio buttons are displaced to the left due to positioning issue of ::before
|
||||
* pseudo element, adding position relative to it's parent fixes the issue.
|
||||
*/
|
||||
.radio-content-field .radio.inline label span.text {
|
||||
position: relative;
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
/**
|
||||
* holiday-weather.com - page is not scrollable
|
||||
* Bug #1575011 - https://bugzilla.mozilla.org/show_bug.cgi?id=1575011
|
||||
* WebCompat issue #18478 - https://webcompat.com/issues/18478
|
||||
*
|
||||
* the page won't scroll since the flex container is too high,
|
||||
* adding min height to parent containers fixes the issue
|
||||
*/
|
||||
.page-container-style__pageContent {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.widgets-style__root {
|
||||
min-height: 0;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* dunkindonuts.com - form input fields are small and misaligned
|
||||
* Bug #1575017 - https://bugzilla.mozilla.org/show_bug.cgi?id=1575017
|
||||
* WebCompat issue #28742 - https://webcompat.com/issues/28742
|
||||
*
|
||||
* Form input fields are small and misaligned due to flex-basis: min-content;
|
||||
* applied on their parent element. Setting it to auto fixes the issue
|
||||
*/
|
||||
.grid__item {
|
||||
flex-basis: auto;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* binance.com - can't see the full site
|
||||
* Bug #1577270 - https://bugzilla.mozilla.org/show_bug.cgi?id=1577270
|
||||
* WebCompat issue #17810 - https://webcompat.com/issues/17810
|
||||
*
|
||||
* The site does not have a doctype and is rendered in quirks mode. The calc() percentage
|
||||
* height is applied on the .main-page .viewWrap element, but its parent does not have
|
||||
* a specified height property. Adding a height of 100% to the parent fixes the issue.
|
||||
*/
|
||||
#tradeDiv {
|
||||
height: 100%;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* kitkat.com.au - can't see the content
|
||||
* Bug #1577297 - https://bugzilla.mozilla.org/show_bug.cgi?id=1577297
|
||||
* WebCompat issue #28992 - https://webcompat.com/issues/28992
|
||||
*
|
||||
* Affected element is too wide due to https://bugzilla.mozilla.org/show_bug.cgi?id=1316534.
|
||||
* Adding min-width: 0; fixes the issue
|
||||
*/
|
||||
.columns .column.main {
|
||||
min-width: 0;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
/* globals exportFunction */
|
||||
|
||||
Object.defineProperty(window.wrappedJSObject, "isTestFeatureSupported", {
|
||||
get: exportFunction(function() {
|
||||
return true;
|
||||
}, window),
|
||||
|
||||
set: exportFunction(function() {}, window),
|
||||
});
|
@ -1,29 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Bug 1452707 - Build site patch for ib.absa.co.za
|
||||
* WebCompat issue #16401 - https://webcompat.com/issues/16401
|
||||
*
|
||||
* The online banking at ib.absa.co.za detect if window.controllers is a
|
||||
* non-falsy value to detect if the current browser is Firefox or something
|
||||
* else. In bug 1448045, this shim has been disabled for Firefox Nightly 61+,
|
||||
* which breaks the UA detection on this site and results in a "Browser
|
||||
* unsuppored" error message.
|
||||
*
|
||||
* This site patch simply sets window.controllers to a string, resulting in
|
||||
* their check to work again.
|
||||
*/
|
||||
|
||||
/* globals exportFunction */
|
||||
|
||||
console.info(
|
||||
"window.controllers has been shimmed for compatibility reasons. See https://webcompat.com/issues/16401 for details."
|
||||
);
|
||||
|
||||
Object.defineProperty(window.wrappedJSObject, "controllers", {
|
||||
get: exportFunction(function() {
|
||||
return true;
|
||||
}, window),
|
||||
|
||||
set: exportFunction(function() {}, window),
|
||||
});
|
@ -1,34 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Bug 1457335 - histography.io - Override UA & navigator.vendor
|
||||
* WebCompat issue #1804 - https://webcompat.com/issues/1804
|
||||
*
|
||||
* This site is using a strict matching of navigator.userAgent and
|
||||
* navigator.vendor to allow access for Safari or Chrome. Here, we set the
|
||||
* values appropriately so we get recognized as Chrome.
|
||||
*/
|
||||
|
||||
/* globals exportFunction */
|
||||
|
||||
console.info(
|
||||
"The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/1804 for details."
|
||||
);
|
||||
|
||||
const CHROME_UA = navigator.userAgent + " Chrome for WebCompat";
|
||||
|
||||
Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", {
|
||||
get: exportFunction(function() {
|
||||
return CHROME_UA;
|
||||
}, window),
|
||||
|
||||
set: exportFunction(function() {}, window),
|
||||
});
|
||||
|
||||
Object.defineProperty(window.navigator.wrappedJSObject, "vendor", {
|
||||
get: exportFunction(function() {
|
||||
return "Google Inc.";
|
||||
}, window),
|
||||
|
||||
set: exportFunction(function() {}, window),
|
||||
});
|
@ -1,48 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Bug 1472075 - Build UA override for Bank of America for OSX & Linux
|
||||
* WebCompat issue #2787 - https://webcompat.com/issues/2787
|
||||
*
|
||||
* BoA is showing a red warning to Linux and macOS users, while accepting
|
||||
* Windows users without warning. From our side, there is no difference here
|
||||
* and we receive a lot of user complains about the warnings, so we spoof
|
||||
* as Firefox on Windows in those cases.
|
||||
*/
|
||||
|
||||
/* globals exportFunction */
|
||||
|
||||
if (!navigator.platform.includes("Win")) {
|
||||
console.info(
|
||||
"The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/2787 for details."
|
||||
);
|
||||
|
||||
const WINDOWS_UA = navigator.userAgent.replace(
|
||||
/\(.*; rv:/i,
|
||||
"(Windows NT 10.0; Win64; x64; rv:"
|
||||
);
|
||||
|
||||
Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", {
|
||||
get: exportFunction(function() {
|
||||
return WINDOWS_UA;
|
||||
}, window),
|
||||
|
||||
set: exportFunction(function() {}, window),
|
||||
});
|
||||
|
||||
Object.defineProperty(window.navigator.wrappedJSObject, "appVersion", {
|
||||
get: exportFunction(function() {
|
||||
return "appVersion";
|
||||
}, window),
|
||||
|
||||
set: exportFunction(function() {}, window),
|
||||
});
|
||||
|
||||
Object.defineProperty(window.navigator.wrappedJSObject, "platform", {
|
||||
get: exportFunction(function() {
|
||||
return "Win64";
|
||||
}, window),
|
||||
|
||||
set: exportFunction(function() {}, window),
|
||||
});
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Bug 1472081 - election.gov.np - Override window.sidebar with something falsey
|
||||
* WebCompat issue #11622 - https://webcompat.com/issues/11622
|
||||
*
|
||||
* This site is blocking onmousedown and onclick if window.sidebar is something
|
||||
* that evaluates to true, rendering the form fields unusable. This patch
|
||||
* overrides window.sidebar with false, so the blocking event handlers won't
|
||||
* get registered.
|
||||
*/
|
||||
|
||||
/* globals exportFunction */
|
||||
|
||||
console.info(
|
||||
"window.sidebar has been shimmed for compatibility reasons. See https://webcompat.com/issues/11622 for details."
|
||||
);
|
||||
|
||||
Object.defineProperty(window.wrappedJSObject, "sidebar", {
|
||||
get: exportFunction(function() {
|
||||
return false;
|
||||
}, window),
|
||||
|
||||
set: exportFunction(function() {}, window),
|
||||
});
|
@ -1,25 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* portalminasnet.com - Override window.sidebar with something falsey
|
||||
* WebCompat issue #18143 - https://webcompat.com/issues/18143
|
||||
*
|
||||
* This site is blocking onmousedown and onclick if window.sidebar is something
|
||||
* that evaluates to true, rendering the login unusable. This patch overrides
|
||||
* window.sidebar with false, so the blocking event handlers won't get
|
||||
* registered.
|
||||
*/
|
||||
|
||||
/* globals exportFunction */
|
||||
|
||||
console.info(
|
||||
"window.sidebar has been shimmed for compatibility reasons. See https://webcompat.com/issues/18143 for details."
|
||||
);
|
||||
|
||||
Object.defineProperty(window.wrappedJSObject, "sidebar", {
|
||||
get: exportFunction(function() {
|
||||
return false;
|
||||
}, window),
|
||||
|
||||
set: exportFunction(function() {}, window),
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user