зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound
This commit is contained in:
Коммит
d13a81eccb
|
@ -7,59 +7,75 @@ module.metadata = {
|
|||
"stability": "experimental"
|
||||
};
|
||||
|
||||
const { Ci, Cc } = require("chrome");
|
||||
const { make: makeWindow, getHiddenWindow } = require("../window/utils");
|
||||
const { create: makeFrame, getDocShell } = require("../frame/utils");
|
||||
const { defer } = require("../core/promise");
|
||||
const { Ci, Cc, Cu } = require("chrome");
|
||||
const { when: unload } = require("../system/unload");
|
||||
const cfxArgs = require("../test/options");
|
||||
const prefs = require("../preferences/service");
|
||||
|
||||
var addonPrincipal = Cc["@mozilla.org/systemprincipal;1"].
|
||||
createInstance(Ci.nsIPrincipal);
|
||||
if (!prefs.get("extensions.usehiddenwindow", false)) {
|
||||
const {HiddenFrame} = require("resource:///modules/HiddenFrame.jsm", {});
|
||||
let hiddenFrame = new HiddenFrame();
|
||||
exports.window = hiddenFrame.getWindow();
|
||||
exports.ready = hiddenFrame.get();
|
||||
|
||||
var hiddenWindow = getHiddenWindow();
|
||||
// Still destroy frame on unload to claim memory back early.
|
||||
// NOTE: this doesn't seem to work and just doesn't get called. :-\
|
||||
unload(function() {
|
||||
hiddenFrame.destroy();
|
||||
hiddenFrame = null;
|
||||
});
|
||||
} else {
|
||||
const { make: makeWindow, getHiddenWindow } = require("../window/utils");
|
||||
const { create: makeFrame, getDocShell } = require("../frame/utils");
|
||||
const { defer } = require("../core/promise");
|
||||
const cfxArgs = require("../test/options");
|
||||
|
||||
if (cfxArgs.parseable) {
|
||||
console.info("hiddenWindow document.documentURI:" +
|
||||
hiddenWindow.document.documentURI);
|
||||
console.info("hiddenWindow document.readyState:" +
|
||||
hiddenWindow.document.readyState);
|
||||
var addonPrincipal = Cc["@mozilla.org/systemprincipal;1"].
|
||||
createInstance(Ci.nsIPrincipal);
|
||||
|
||||
var hiddenWindow = getHiddenWindow();
|
||||
|
||||
if (cfxArgs.parseable) {
|
||||
console.info("hiddenWindow document.documentURI:" +
|
||||
hiddenWindow.document.documentURI);
|
||||
console.info("hiddenWindow document.readyState:" +
|
||||
hiddenWindow.document.readyState);
|
||||
}
|
||||
|
||||
// Once Bug 565388 is fixed and shipped we'll be able to make invisible,
|
||||
// permanent docShells. Meanwhile we create hidden top level window and
|
||||
// use it's docShell.
|
||||
var frame = makeFrame(hiddenWindow.document, {
|
||||
nodeName: "iframe",
|
||||
namespaceURI: "http://www.w3.org/1999/xhtml",
|
||||
allowJavascript: true,
|
||||
allowPlugins: true
|
||||
})
|
||||
var docShell = getDocShell(frame);
|
||||
var eventTarget = docShell.chromeEventHandler;
|
||||
|
||||
// We need to grant docShell system principals in order to load XUL document
|
||||
// from data URI into it.
|
||||
docShell.createAboutBlankContentViewer(addonPrincipal);
|
||||
|
||||
// Get a reference to the DOM window of the given docShell and load
|
||||
// such document into that would allow us to create XUL iframes, that
|
||||
// are necessary for hidden frames etc..
|
||||
var window = docShell.contentViewer.DOMDocument.defaultView;
|
||||
window.location = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window/>";
|
||||
|
||||
// Create a promise that is delivered once add-on window is interactive,
|
||||
// used by add-on runner to defer add-on loading until window is ready.
|
||||
var { promise, resolve } = defer();
|
||||
eventTarget.addEventListener("DOMContentLoaded", function(event) {
|
||||
resolve();
|
||||
}, {once: true});
|
||||
|
||||
exports.ready = promise;
|
||||
exports.window = window;
|
||||
|
||||
// Still close window on unload to claim memory back early.
|
||||
unload(function() {
|
||||
window.close()
|
||||
frame.remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Once Bug 565388 is fixed and shipped we'll be able to make invisible,
|
||||
// permanent docShells. Meanwhile we create hidden top level window and
|
||||
// use it's docShell.
|
||||
var frame = makeFrame(hiddenWindow.document, {
|
||||
nodeName: "iframe",
|
||||
namespaceURI: "http://www.w3.org/1999/xhtml",
|
||||
allowJavascript: true,
|
||||
allowPlugins: true
|
||||
})
|
||||
var docShell = getDocShell(frame);
|
||||
var eventTarget = docShell.chromeEventHandler;
|
||||
|
||||
// We need to grant docShell system principals in order to load XUL document
|
||||
// from data URI into it.
|
||||
docShell.createAboutBlankContentViewer(addonPrincipal);
|
||||
|
||||
// Get a reference to the DOM window of the given docShell and load
|
||||
// such document into that would allow us to create XUL iframes, that
|
||||
// are necessary for hidden frames etc..
|
||||
var window = docShell.contentViewer.DOMDocument.defaultView;
|
||||
window.location = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window/>";
|
||||
|
||||
// Create a promise that is delivered once add-on window is interactive,
|
||||
// used by add-on runner to defer add-on loading until window is ready.
|
||||
var { promise, resolve } = defer();
|
||||
eventTarget.addEventListener("DOMContentLoaded", function(event) {
|
||||
resolve();
|
||||
}, {once: true});
|
||||
|
||||
exports.ready = promise;
|
||||
exports.window = window;
|
||||
|
||||
// Still close window on unload to claim memory back early.
|
||||
unload(function() {
|
||||
window.close()
|
||||
frame.remove();
|
||||
});
|
||||
|
|
|
@ -8,8 +8,9 @@ module.metadata = {
|
|||
};
|
||||
|
||||
const { deprecateFunction } = require("../util/deprecate");
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const XMLHttpRequest = require("../addon/window").window.XMLHttpRequest;
|
||||
const { Ci, Cu } = require("chrome");
|
||||
|
||||
Cu.importGlobalProperties(["XMLHttpRequest"]);
|
||||
|
||||
Object.defineProperties(XMLHttpRequest.prototype, {
|
||||
mozBackgroundRequest: {
|
||||
|
|
|
@ -13,7 +13,7 @@ const { Services } = require("resource://gre/modules/Services.jsm");
|
|||
const { setTimeout } = require("../timers");
|
||||
const { platform } = require("../system");
|
||||
const { getMostRecentBrowserWindow, getOwnerBrowserWindow,
|
||||
getHiddenWindow, getScreenPixelsPerCSSPixel } = require("../window/utils");
|
||||
getScreenPixelsPerCSSPixel } = require("../window/utils");
|
||||
|
||||
const { create: createFrame, swapFrameLoaders, getDocShell } = require("../frame/utils");
|
||||
const { window: addonWindow } = require("../addon/window");
|
||||
|
|
|
@ -426,6 +426,8 @@ pref("browser.link.open_newwindow.disabled_in_fullscreen", true);
|
|||
pref("browser.link.open_newwindow.disabled_in_fullscreen", false);
|
||||
#endif
|
||||
|
||||
pref("browser.photon.structure.enabled", false);
|
||||
|
||||
// Tabbed browser
|
||||
pref("browser.tabs.closeWindowWithLastTab", true);
|
||||
pref("browser.tabs.insertRelatedAfterCurrent", true);
|
||||
|
|
|
@ -52,6 +52,17 @@ function init(aEvent) {
|
|||
let arch = bundle.GetStringFromName(archResource);
|
||||
versionField.textContent += ` (${arch})`;
|
||||
|
||||
// Show a release notes link if we have a URL.
|
||||
let relNotesLink = document.getElementById("releasenotes");
|
||||
let relNotesPrefType = Services.prefs.getPrefType("app.releaseNotesURL");
|
||||
if (relNotesPrefType != Services.prefs.PREF_INVALID) {
|
||||
let relNotesURL = Services.urlFormatter.formatURLPref("app.releaseNotesURL");
|
||||
if (relNotesURL != "about:blank") {
|
||||
relNotesLink.href = relNotesURL;
|
||||
relNotesLink.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (AppConstants.MOZ_UPDATER) {
|
||||
gAppUpdater = new appUpdater();
|
||||
|
||||
|
|
|
@ -45,10 +45,8 @@
|
|||
<vbox id="leftBox" flex="1"/>
|
||||
<vbox id="rightBox" flex="1">
|
||||
<hbox align="baseline">
|
||||
#expand <label id="version">__MOZ_APP_VERSION_DISPLAY__</label>
|
||||
#ifndef NIGHTLY_BUILD
|
||||
#expand <label id="releasenotes" class="text-link" href="https://www.mozilla.org/firefox/__MOZ_APP_VERSION__/releasenotes/">&releaseNotes.link;</label>
|
||||
#endif
|
||||
#expand <label id="version">__MOZ_APP_VERSION_DISPLAY__</label>
|
||||
<label id="releasenotes" class="text-link" hidden="true">&releaseNotes.link;</label>
|
||||
</hbox>
|
||||
|
||||
<label id="distribution" class="text-blurb"/>
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="gBrowserInit.onLoad()" onunload="gBrowserInit.onUnload()" onclose="return WindowIsClosing();"
|
||||
title="&mainWindow.title;"
|
||||
|
@ -719,6 +720,8 @@
|
|||
tooltiptext="&urlbar.microphoneBlocked.tooltip;"/>
|
||||
<image data-permission-id="screen" class="blocked-permission-icon screen-icon" role="button"
|
||||
tooltiptext="&urlbar.screenBlocked.tooltip;"/>
|
||||
<image data-permission-id="persistent-storage" class="blocked-permission-icon persistent-storage-icon" role="button"
|
||||
tooltiptext="&urlbar.persistentStorageBlocked.tooltip;"/>
|
||||
</box>
|
||||
<box id="notification-popup-box"
|
||||
hidden="true"
|
||||
|
@ -753,6 +756,8 @@
|
|||
tooltiptext="&urlbar.translatedNotificationAnchor.tooltip;"/>
|
||||
<image id="eme-notification-icon" class="notification-anchor-icon drm-icon" role="button"
|
||||
tooltiptext="&urlbar.emeNotificationAnchor.tooltip;"/>
|
||||
<image id="persistent-storage-notification-icon" class="notification-anchor-icon persistent-storage-icon" role="button"
|
||||
tooltiptext="&urlbar.persistentStorageNotificationAnchor.tooltip;"/>
|
||||
</box>
|
||||
<image id="connection-icon"/>
|
||||
<hbox id="identity-icon-labels">
|
||||
|
|
|
@ -355,7 +355,9 @@ var AboutNetAndCertErrorListener = {
|
|||
|
||||
// If the difference is more than a day.
|
||||
if (Math.abs(difference) > 60 * 60 * 24) {
|
||||
let formatter = new Intl.DateTimeFormat();
|
||||
let formatter = Services.intl.createDateTimeFormat(undefined, {
|
||||
dateStyle: "short"
|
||||
});
|
||||
let systemDate = formatter.format(new Date());
|
||||
// negative difference means local time is behind server time
|
||||
let actualDate = formatter.format(new Date(Date.now() - difference * 1000));
|
||||
|
@ -385,7 +387,9 @@ var AboutNetAndCertErrorListener = {
|
|||
let systemDate = new Date();
|
||||
|
||||
if (buildDate > systemDate) {
|
||||
let formatter = new Intl.DateTimeFormat();
|
||||
let formatter = Services.intl.createDateTimeFormat(undefined, {
|
||||
dateStyle: "short"
|
||||
});
|
||||
|
||||
content.document.getElementById("wrongSystemTimeWithoutReference_URL")
|
||||
.textContent = content.document.location.hostname;
|
||||
|
|
|
@ -74,7 +74,6 @@
|
|||
|
||||
<popupnotification id="addon-webext-permissions-notification" hidden="true">
|
||||
<popupnotificationcontent orient="vertical">
|
||||
<description id="addon-webext-perm-header" class="addon-webext-perm-header"/>
|
||||
<description id="addon-webext-perm-text" class="addon-webext-perm-text"/>
|
||||
<label id="addon-webext-perm-intro" class="addon-webext-perm-text"/>
|
||||
<html:ul id="addon-webext-perm-list" class="addon-webext-perm-list"/>
|
||||
|
|
|
@ -151,7 +151,9 @@ add_task(function* checkWrongSystemTimeWarning() {
|
|||
});
|
||||
}
|
||||
|
||||
let formatter = new Intl.DateTimeFormat();
|
||||
let formatter = Services.intl.createDateTimeFormat(undefined, {
|
||||
dateStyle: "short"
|
||||
});
|
||||
|
||||
// pretend we have a positively skewed (ahead) system time
|
||||
let serverDate = new Date("2015/10/27");
|
||||
|
|
|
@ -160,6 +160,23 @@ var tests = [
|
|||
gNotification.remove();
|
||||
}
|
||||
},
|
||||
|
||||
// Test no checkbox hides warning label
|
||||
{ id: "no_checkbox",
|
||||
run() {
|
||||
this.notifyObj = new BasicNotification(this.id);
|
||||
this.notifyObj.options.checkbox = null;
|
||||
gNotification = showNotification(this.notifyObj);
|
||||
},
|
||||
onShown(popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
let notification = popup.childNodes[0];
|
||||
checkCheckbox(notification.checkbox, "", false, true);
|
||||
checkMainAction(notification);
|
||||
triggerMainCommand(popup);
|
||||
},
|
||||
onHidden() { },
|
||||
},
|
||||
];
|
||||
|
||||
// Test checkbox disabling the main action in different combinations
|
||||
|
|
|
@ -22,6 +22,8 @@ pref("app.update.url.manual", "https://www.mozilla.org/firefox/aurora/");
|
|||
// supplied in the "An update is available" page of the update wizard.
|
||||
pref("app.update.url.details", "https://www.mozilla.org/firefox/aurora/");
|
||||
|
||||
pref("app.releaseNotesURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=whatsnew");
|
||||
|
||||
// The number of days a binary is permitted to be old
|
||||
// without checking for an update. This assumes that
|
||||
// app.update.checkInstallTime is true.
|
||||
|
|
|
@ -20,6 +20,8 @@ pref("app.update.url.manual", "https://www.mozilla.org/firefox/");
|
|||
// supplied in the "An update is available" page of the update wizard.
|
||||
pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/notes");
|
||||
|
||||
pref("app.releaseNotesURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=whatsnew");
|
||||
|
||||
// The number of days a binary is permitted to be old
|
||||
// without checking for an update. This assumes that
|
||||
// app.update.checkInstallTime is true.
|
||||
|
|
|
@ -485,3 +485,13 @@
|
|||
</popupnotificationcontent>
|
||||
</popupnotification>
|
||||
</panel>
|
||||
|
||||
<panel id="PanelUI-photon-popup"
|
||||
role="group"
|
||||
type="arrow"
|
||||
hidden="true"
|
||||
flip="slide"
|
||||
position="bottomcenter topright"
|
||||
noautofocus="true">
|
||||
This space intentionally left blank.
|
||||
</panel>
|
||||
|
|
|
@ -11,6 +11,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
||||
"resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "gPhotonStructure",
|
||||
"browser.photon.structure.enabled", false);
|
||||
|
||||
/**
|
||||
* Maintains the state and dispatches events for the main menu panel.
|
||||
*/
|
||||
|
@ -31,7 +34,7 @@ const PanelUI = {
|
|||
multiView: "PanelUI-multiView",
|
||||
helpView: "PanelUI-helpView",
|
||||
menuButton: "PanelUI-menu-button",
|
||||
panel: "PanelUI-popup",
|
||||
panel: gPhotonStructure ? "PanelUI-photon-popup" : "PanelUI-popup",
|
||||
notificationPanel: "PanelUI-notification-popup",
|
||||
scroller: "PanelUI-contents-scroller",
|
||||
footer: "PanelUI-footer"
|
||||
|
|
|
@ -189,9 +189,11 @@ FeedWriter.prototype = {
|
|||
__dateFormatter: null,
|
||||
get _dateFormatter() {
|
||||
if (!this.__dateFormatter) {
|
||||
const dtOptions = { year: "numeric", month: "long", day: "numeric",
|
||||
hour: "numeric", minute: "numeric" };
|
||||
this.__dateFormatter = new Intl.DateTimeFormat(undefined, dtOptions);
|
||||
const dtOptions = {
|
||||
timeStyle: "short",
|
||||
dateStyle: "long"
|
||||
};
|
||||
this.__dateFormatter = Services.intl.createDateTimeFormat(undefined, dtOptions);
|
||||
}
|
||||
return this.__dateFormatter;
|
||||
},
|
||||
|
|
|
@ -2315,6 +2315,11 @@ const ContentPermissionIntegration = {
|
|||
case "desktop-notification": {
|
||||
return new PermissionUI.DesktopNotificationPermissionPrompt(request);
|
||||
}
|
||||
case "persistent-storage": {
|
||||
if (Services.prefs.getBoolPref("browser.storageManager.enabled")) {
|
||||
return new PermissionUI.PersistentStoragePermissionPrompt(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
Components.utils.import("resource://gre/modules/AppConstants.jsm");
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/TelemetryStopwatch.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "MigrationUtils",
|
||||
"resource:///modules/MigrationUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
|
@ -411,8 +412,10 @@ var PlacesOrganizer = {
|
|||
populateRestoreMenu: function PO_populateRestoreMenu() {
|
||||
let restorePopup = document.getElementById("fileRestorePopup");
|
||||
|
||||
const dtOptions = { year: "numeric", month: "long", day: "numeric" };
|
||||
let dateFormatter = new Intl.DateTimeFormat(undefined, dtOptions);
|
||||
const dtOptions = {
|
||||
dateStyle: "long"
|
||||
};
|
||||
let dateFormatter = Services.intl.createDateTimeFormat(undefined, dtOptions);
|
||||
|
||||
// Remove existing menu items. Last item is the restoreFromFile item.
|
||||
while (restorePopup.childNodes.length > 1)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const PTV_interfaces = [Ci.nsITreeView,
|
||||
Ci.nsINavHistoryResultObserver,
|
||||
|
@ -499,8 +500,8 @@ PlacesTreeView.prototype = {
|
|||
__todayFormatter: null,
|
||||
get _todayFormatter() {
|
||||
if (!this.__todayFormatter) {
|
||||
const dtOptions = { hour: "numeric", minute: "numeric" };
|
||||
this.__todayFormatter = new Intl.DateTimeFormat(undefined, dtOptions);
|
||||
const dtOptions = { timeStyle: "short" };
|
||||
this.__todayFormatter = Services.intl.createDateTimeFormat(undefined, dtOptions);
|
||||
}
|
||||
return this.__todayFormatter;
|
||||
},
|
||||
|
@ -508,9 +509,11 @@ PlacesTreeView.prototype = {
|
|||
__dateFormatter: null,
|
||||
get _dateFormatter() {
|
||||
if (!this.__dateFormatter) {
|
||||
const dtOptions = { year: "numeric", month: "numeric", day: "numeric",
|
||||
hour: "numeric", minute: "numeric" };
|
||||
this.__dateFormatter = new Intl.DateTimeFormat(undefined, dtOptions);
|
||||
const dtOptions = {
|
||||
dateStyle: "short",
|
||||
timeStyle: "short"
|
||||
};
|
||||
this.__dateFormatter = Services.intl.createDateTimeFormat(undefined, dtOptions);
|
||||
}
|
||||
return this.__dateFormatter;
|
||||
},
|
||||
|
|
|
@ -124,18 +124,21 @@
|
|||
case "date":
|
||||
let timeObj = new Date(node.time / 1000);
|
||||
// Default is short date format.
|
||||
let dtOptions = { year: 'numeric', month: 'numeric', day: 'numeric',
|
||||
hour: 'numeric', minute: 'numeric' };
|
||||
let dtOptions = {
|
||||
dateStyle: "short",
|
||||
timeStyle: "short"
|
||||
};
|
||||
|
||||
// For today's visits we don't show date portion.
|
||||
if (node.uri == "http://at.midnight.com/" ||
|
||||
node.uri == "http://after.midnight.com/") {
|
||||
dtOptions = { hour: 'numeric', minute: 'numeric' };
|
||||
dtOptions.dateStyle = undefined;
|
||||
} else if (node.uri != "http://before.midnight.com/") {
|
||||
// Avoid to test spurious uris, due to how the test works
|
||||
// a redirecting uri could be put in the tree while we test.
|
||||
break;
|
||||
}
|
||||
let timeStr = timeObj.toLocaleString(undefined, dtOptions);
|
||||
let timeStr = Services.intl.createDateTimeFormat(undefined, dtOptions).format(timeObj);
|
||||
|
||||
is(text, timeStr, "Date format is correct");
|
||||
break;
|
||||
|
|
|
@ -213,6 +213,7 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
|||
<!ENTITY urlbar.passwordNotificationAnchor.tooltip "Open save password message panel">
|
||||
<!ENTITY urlbar.pluginsNotificationAnchor.tooltip "Manage plug-in use">
|
||||
<!ENTITY urlbar.webNotificationAnchor.tooltip "Change whether you can receive notifications from the site">
|
||||
<!ENTITY urlbar.persistentStorageNotificationAnchor.tooltip "Store data in Persistent Storage">
|
||||
|
||||
<!ENTITY urlbar.webRTCShareDevicesNotificationAnchor.tooltip "Manage sharing your camera and/or microphone with the site">
|
||||
<!ENTITY urlbar.webRTCShareMicrophoneNotificationAnchor.tooltip "Manage sharing your microphone with the site">
|
||||
|
@ -229,12 +230,13 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
|||
<!ENTITY urlbar.geolocationBlocked.tooltip "You have blocked location information for this website.">
|
||||
<!ENTITY urlbar.indexedDBBlocked.tooltip "You have blocked data storage for this website.">
|
||||
<!ENTITY urlbar.webNotificationsBlocked.tooltip "You have blocked notifications for this website.">
|
||||
<!ENTITY urlbar.persistentStorageBlocked.tooltip "You have blocked persistent storage for this website.">
|
||||
|
||||
<!ENTITY urlbar.openHistoryPopup.tooltip "Show history">
|
||||
|
||||
<!ENTITY searchItem.title "Search">
|
||||
|
||||
<!-- Toolbar items -->
|
||||
<!-- Toolbar items -->
|
||||
<!ENTITY homeButton.label "Home">
|
||||
|
||||
<!ENTITY bookmarksButton.label "Bookmarks">
|
||||
|
|
|
@ -496,6 +496,14 @@ geolocation.shareWithSite3=Will you allow %S to access your location?
|
|||
geolocation.shareWithFile3=Will you allow this local file to access your location?
|
||||
geolocation.remember=Remember this decision
|
||||
|
||||
# Persistent storage UI
|
||||
persistentStorage.allow=Allow
|
||||
persistentStorage.allow.accesskey=A
|
||||
persistentStorage.dontAllow=Don’t Allow
|
||||
persistentStorage.dontAllow.accesskey=n
|
||||
persistentStorage.allowWithSite=Will you allow %S to store data in persistent storage?
|
||||
persistentStorage.remember=Remember this decision
|
||||
|
||||
webNotifications.allow=Allow Notifications
|
||||
webNotifications.allow.accesskey=A
|
||||
webNotifications.notNow=Not Now
|
||||
|
|
|
@ -36,3 +36,4 @@ permission.popup.label = Open Pop-up Windows
|
|||
permission.geo.label = Access Your Location
|
||||
permission.indexedDB.label = Maintain Offline Storage
|
||||
permission.focus-tab-by-prompt.label = Switch to this Tab
|
||||
permission.persistent-storage.label = Store Data in Persistent Storage
|
||||
|
|
|
@ -63,6 +63,7 @@ mk
|
|||
ml
|
||||
mr
|
||||
ms
|
||||
my
|
||||
nb-NO
|
||||
nl
|
||||
nn-NO
|
||||
|
|
|
@ -341,10 +341,11 @@ this.ExtensionsUI = {
|
|||
|
||||
showPermissionsPrompt(browser, strings, icon, histkey) {
|
||||
function eventCallback(topic) {
|
||||
if (topic == "showing") {
|
||||
let doc = this.browser.ownerDocument;
|
||||
doc.getElementById("addon-webext-perm-header").innerHTML = strings.header;
|
||||
|
||||
let doc = this.browser.ownerDocument;
|
||||
if (topic == "shown") {
|
||||
doc.getElementById("addon-webext-permissions-notification")
|
||||
.description.innerHTML = strings.header;
|
||||
} else if (topic == "showing") {
|
||||
let textEl = doc.getElementById("addon-webext-perm-text");
|
||||
textEl.innerHTML = strings.text;
|
||||
textEl.hidden = !strings.text;
|
||||
|
@ -397,7 +398,12 @@ this.ExtensionsUI = {
|
|||
},
|
||||
];
|
||||
|
||||
win.PopupNotifications.show(browser, "addon-webext-permissions", "",
|
||||
// Get the text value of strings.header to pre-populate the header. This will get
|
||||
// overwritten with the HTML version later.
|
||||
let escapeHeader = browser.ownerDocument.createElement("div");
|
||||
escapeHeader.innerHTML = strings.header;
|
||||
win.PopupNotifications.show(browser, "addon-webext-permissions",
|
||||
escapeHeader.textContent,
|
||||
"addons-notification-icon",
|
||||
action, secondaryActions, popupOptions);
|
||||
});
|
||||
|
|
|
@ -14,11 +14,27 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
|
||||
const XUL_PAGE = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window%20id='win'/>";
|
||||
|
||||
const gAllHiddenFrames = new WeakSet();
|
||||
|
||||
let cleanupRegistered = false;
|
||||
function ensureCleanupRegistered() {
|
||||
if (!cleanupRegistered) {
|
||||
cleanupRegistered = true;
|
||||
Services.obs.addObserver(function() {
|
||||
for (let hiddenFrame of ChromeUtils.nondeterministicGetWeakSetKeys(gAllHiddenFrames)) {
|
||||
hiddenFrame.destroy();
|
||||
}
|
||||
}, "xpcom-shutdown", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An hidden frame object. It takes care of creating a windowless browser and
|
||||
* passing the window containing a blank XUL <window> back.
|
||||
*/
|
||||
function HiddenFrame() {}
|
||||
function HiddenFrame() {
|
||||
}
|
||||
|
||||
HiddenFrame.prototype = {
|
||||
_frame: null,
|
||||
|
@ -41,6 +57,16 @@ HiddenFrame.prototype = {
|
|||
return this._deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch a sync ref to the window inside the frame (needed for the add-on SDK).
|
||||
*/
|
||||
getWindow() {
|
||||
this.get();
|
||||
this._browser.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
return this._browser.getInterface(Ci.nsIDOMWindow);
|
||||
},
|
||||
|
||||
|
||||
destroy() {
|
||||
if (this._browser) {
|
||||
if (this._listener) {
|
||||
|
@ -51,14 +77,17 @@ HiddenFrame.prototype = {
|
|||
this._frame = null;
|
||||
this._deferred = null;
|
||||
|
||||
gAllHiddenFrames.delete(this);
|
||||
this._browser.close();
|
||||
this._browser = null;
|
||||
}
|
||||
},
|
||||
|
||||
_create() {
|
||||
ensureCleanupRegistered();
|
||||
this._browser = Services.appShell.createWindowlessBrowser(true);
|
||||
this._browser.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
gAllHiddenFrames.add(this);
|
||||
this._webProgress = this._browser.getInterface(Ci.nsIWebProgress);
|
||||
this._listener = {
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
|
|
|
@ -598,3 +598,75 @@ DesktopNotificationPermissionPrompt.prototype = {
|
|||
|
||||
PermissionUI.DesktopNotificationPermissionPrompt =
|
||||
DesktopNotificationPermissionPrompt;
|
||||
|
||||
/**
|
||||
* Creates a PermissionPrompt for a nsIContentPermissionRequest for
|
||||
* the persistent-storage API.
|
||||
*
|
||||
* @param request (nsIContentPermissionRequest)
|
||||
* The request for a permission from content.
|
||||
*/
|
||||
function PersistentStoragePermissionPrompt(request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
PersistentStoragePermissionPrompt.prototype = {
|
||||
__proto__: PermissionPromptForRequestPrototype,
|
||||
|
||||
get permissionKey() {
|
||||
return "persistent-storage";
|
||||
},
|
||||
|
||||
get popupOptions() {
|
||||
let checkbox = {
|
||||
// In PB mode, we don't want the "always remember" checkbox
|
||||
show: !PrivateBrowsingUtils.isWindowPrivate(this.browser.ownerGlobal)
|
||||
};
|
||||
if (checkbox.show) {
|
||||
checkbox.checked = true;
|
||||
checkbox.label = gBrowserBundle.GetStringFromName("persistentStorage.remember");
|
||||
}
|
||||
let learnMoreURL =
|
||||
Services.urlFormatter.formatURLPref("app.support.baseURL") + "storage-permissions";
|
||||
return {
|
||||
checkbox,
|
||||
learnMoreURL
|
||||
};
|
||||
},
|
||||
|
||||
get notificationID() {
|
||||
return "persistent-storage";
|
||||
},
|
||||
|
||||
get anchorID() {
|
||||
return "persistent-storage-notification-icon";
|
||||
},
|
||||
|
||||
get message() {
|
||||
let hostPort = "<>";
|
||||
try {
|
||||
hostPort = this.principal.URI.hostPort;
|
||||
} catch (ex) {}
|
||||
return gBrowserBundle.formatStringFromName(
|
||||
"persistentStorage.allowWithSite", [hostPort], 1);
|
||||
},
|
||||
|
||||
get promptActions() {
|
||||
return [
|
||||
{
|
||||
label: gBrowserBundle.GetStringFromName("persistentStorage.allow"),
|
||||
accessKey:
|
||||
gBrowserBundle.GetStringFromName("persistentStorage.allow.accesskey"),
|
||||
action: Ci.nsIPermissionManager.ALLOW_ACTION
|
||||
},
|
||||
{
|
||||
label: gBrowserBundle.GetStringFromName("persistentStorage.dontAllow"),
|
||||
accessKey:
|
||||
gBrowserBundle.GetStringFromName("persistentStorage.dontAllow.accesskey"),
|
||||
action: Ci.nsIPermissionManager.DENY_ACTION
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
PermissionUI.PersistentStoragePermissionPrompt = PersistentStoragePermissionPrompt;
|
||||
|
|
|
@ -614,8 +614,16 @@ var gPermissionObject = {
|
|||
exactHostMatch: true,
|
||||
states: [ SitePermissions.UNKNOWN, SitePermissions.ALLOW ],
|
||||
},
|
||||
"persistent-storage": {
|
||||
exactHostMatch: true
|
||||
}
|
||||
};
|
||||
|
||||
// Delete this entry while being pre-off
|
||||
// or the persistent-storage permission would appear in Page info's Permission section
|
||||
if (!Services.prefs.getBoolPref("browser.storageManager.enabled")) {
|
||||
delete gPermissionObject["persistent-storage"];
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(SitePermissions, "temporaryPermissionExpireTime",
|
||||
"privacy.temporary_permission_expire_time_ms", 3600 * 1000);
|
||||
|
||||
|
|
|
@ -20,6 +20,11 @@ add_task(function* test_desktop_notification_permission_prompt() {
|
|||
yield testPrompt(PermissionUI.DesktopNotificationPermissionPrompt);
|
||||
});
|
||||
|
||||
// Tests that PersistentStoragePermissionPrompt works as expected
|
||||
add_task(function* test_persistent_storage_permission_prompt() {
|
||||
yield testPrompt(PermissionUI.PersistentStoragePermissionPrompt);
|
||||
});
|
||||
|
||||
function* testPrompt(Prompt) {
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
|
|
|
@ -9,7 +9,7 @@ Components.utils.import("resource://gre/modules/Services.jsm");
|
|||
add_task(function* testPermissionsListing() {
|
||||
Assert.deepEqual(SitePermissions.listPermissions().sort(),
|
||||
["camera", "cookie", "desktop-notification", "focus-tab-by-prompt", "geo", "image",
|
||||
"indexedDB", "install", "microphone", "popup", "screen"],
|
||||
"indexedDB", "install", "microphone", "persistent-storage", "popup", "screen"],
|
||||
"Correct list of all permissions");
|
||||
});
|
||||
|
||||
|
@ -72,7 +72,8 @@ add_task(function* testExactHostMatch() {
|
|||
let uri = Services.io.newURI("https://example.com");
|
||||
let subUri = Services.io.newURI("https://test1.example.com");
|
||||
|
||||
let exactHostMatched = ["desktop-notification", "focus-tab-by-prompt", "camera", "microphone", "screen", "geo"];
|
||||
let exactHostMatched = ["desktop-notification", "focus-tab-by-prompt", "camera",
|
||||
"microphone", "screen", "geo", "persistent-storage"];
|
||||
let nonExactHostMatched = ["image", "cookie", "popup", "install", "indexedDB"];
|
||||
|
||||
let permissions = SitePermissions.listPermissions();
|
||||
|
|
|
@ -793,8 +793,18 @@ menuitem.bookmark-item {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.addon-webext-perm-header {
|
||||
font-size: 1.3em;
|
||||
html|*.addon-webext-perm-list {
|
||||
margin-block-end: 0;
|
||||
padding-inline-start: 10px;
|
||||
}
|
||||
|
||||
.addon-webext-perm-text {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.popup-notification-description[popupid="addon-webext-permissions"] {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.addon-webext-name {
|
||||
|
|
|
@ -3031,8 +3031,18 @@ menulist.translate-infobar-element > .menulist-dropmarker {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.addon-webext-perm-header {
|
||||
font-size: 1.3em;
|
||||
html|*.addon-webext-perm-list {
|
||||
margin-block-end: 0;
|
||||
padding-inline-start: 10px;
|
||||
}
|
||||
|
||||
.addon-webext-perm-text {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.popup-notification-description[popupid="addon-webext-permissions"] {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.addon-webext-name {
|
||||
|
|
|
@ -37,6 +37,15 @@
|
|||
list-style-image: url(chrome://browser/skin/notification-icons.svg#focus-tab-by-prompt);
|
||||
}
|
||||
|
||||
.popup-notification-icon[popupid="persistent-storage"],
|
||||
.persistent-storage-icon {
|
||||
list-style-image: url(chrome://browser/skin/notification-icons.svg#persistent-storage);
|
||||
}
|
||||
|
||||
.persistent-storage-icon.blocked-permission-icon {
|
||||
list-style-image: url(chrome://browser/skin/notification-icons.svg#persistent-storage-blocked);
|
||||
}
|
||||
|
||||
.popup-notification-icon[popupid="web-notifications"],
|
||||
.desktop-notification-icon {
|
||||
list-style-image: url(chrome://browser/skin/notification-icons.svg#desktop-notification);
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
<path id="login-detailed-icon" d="m 1,27 0,3.5 a 0.5,0.5 0 0 0 0.5,0.5 l 5,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1.5 1.5,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1.5 1,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1 1,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-2 2,0 2.5,-2.5 q 0.5,-0.5 1,0 l 1,1 c 0.5,0.5 1,0.5 1.5,-0.5 l 1,-2 a 9,9 0 1 0 -8,-8 l -2,1 c -1,0.5 -1,1 -0.5,1.5 l 1.5,1.5 q 0.5,0.5 0,1 z m 21,-19.1 a 2,2 0 1 1 0,0.2 z" />
|
||||
<path id="microphone-icon" d="m 8,14 0,4 a 8,8 0 0 0 6,7.7 l 0,2.3 -2,0 a 2,2 0 0 0 -2,2 l 12,0 a 2,2 0 0 0 -2,-2 l -2,0 0,-2.3 a 8,8 0 0 0 6,-7.7 l 0,-4 -2,0 0,4 a 6,6 0 0 1 -12,0 l 0,-4 z m 4,4 a 4,4 0 0 0 8,0 l 0,-12 a 4,4 0 0 0 -8,0 z" />
|
||||
<path id="microphone-detailed-icon" d="m 8,18 a 8,8 0 0 0 6,7.7 l 0,2.3 -1,0 a 3,2 0 0 0 -3,2 l 12,0 a 3,2 0 0 0 -3,-2 l -1,0 0,-2.3 a 8,8 0 0 0 6,-7.7 l 0,-4 a 1,1 0 0 0 -2,0 l 0,4 a 6,6 0 0 1 -12,0 l 0,-4 a 1,1 0 0 0 -2,0 z m 4,0 a 4,4 0 0 0 8,0 l 0,-12 a 4,4 0 0 0 -8,0 z" />
|
||||
<path id="persistent-storage-icon" d="M26 21.1H6c-1.1 0-2 .9-2 2V27c0 1.1.9 2 2 2h20c1.1 0 2-.9 2-2v-3.9c0-1.1-.9-2-2-2zM24.1 27c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM25 3H7C5.3 3 4 4.4 4 6.2v13.3c.6-.3 1.3-.5 2-.5h20c.7 0 1.4.2 2 .5V6.2C28 4.4 26.7 3 25 3z"/>
|
||||
<path id="plugin-icon" d="m 2,26 a 2,2 0 0 0 2,2 l 24,0 a 2,2 0 0 0 2,-2 l 0,-16 a 2,2 0 0 0 -2,-2 l -24,0 a 2,2 0 0 0 -2,2 z m 2,-20 10,0 0,-2 a 2,2 0 0 0 -2,-2 l -6,0 a 2,2 0 0 0 -2,2 z m 14,0 10,0 0,-2 a 2,2 0 0 0 -2,-2 l -6,0 a 2,2 0 0 0 -2,2 z" />
|
||||
<path id="popup-icon" d="m 2,24 a 4,4 0 0 0 4,4 l 8,0 a 10,10 0 0 1 -2,-4 l -4,0 a 2,2 0 0 1 -2,-2 l 0,-12 18,0 0,2 a 10,10 0 0 1 4,2 l 0,-8 a 4,4 0 0 0 -4,-4 l -18,0 a 4,4 0 0 0 -4,4 z m 12,-2.1 a 8,8 0 1 1 0,0.2 m 10.7,-4.3 a 5,5 0 0 0 -6.9,6.9 z m -5.4,8.4 a 5,5 0 0 0 6.9,-6.9 z" />
|
||||
<path id="screen-icon" d="m 2,18 a 2,2 0 0 0 2,2 l 2,0 0,-6 a 4,4 0 0 1 4,-4 l 14,0 0,-6 a 2,2 0 0 0 -2,-2 l -18,0 a 2,2 0 0 0 -2,2 z m 6,10 a 2,2 0 0 0 2,2 l 18,0 a 2,2 0 0 0 2,-2 l 0,-14 a 2,2 0 0 0 -2,-2 l -18,0 a 2,2 0 0 0 -2,2 z" />
|
||||
|
@ -111,6 +112,8 @@
|
|||
<use id="microphone-indicator" xlink:href="#microphone-icon"/>
|
||||
<use id="microphone-blocked" class="blocked" xlink:href="#microphone-icon" />
|
||||
<use id="microphone-detailed" xlink:href="#microphone-detailed-icon" />
|
||||
<use id="persistent-storage" xlink:href="#persistent-storage-icon" />
|
||||
<use id="persistent-storage-blocked" class="blocked" xlink:href="#persistent-storage-icon" />
|
||||
<use id="plugin" xlink:href="#plugin-icon" />
|
||||
<use id="plugin-blocked" class="blocked" xlink:href="#plugin-icon" />
|
||||
<use id="popup" xlink:href="#popup-icon" />
|
||||
|
|
До Ширина: | Высота: | Размер: 7.8 KiB После Ширина: | Высота: | Размер: 8.2 KiB |
|
@ -2074,8 +2074,18 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.addon-webext-perm-header {
|
||||
font-size: 1.3em;
|
||||
html|*.addon-webext-perm-list {
|
||||
margin-block-end: 0;
|
||||
padding-inline-start: 10px;
|
||||
}
|
||||
|
||||
.addon-webext-perm-text {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.popup-notification-description[popupid="addon-webext-permissions"] {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.addon-webext-name {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
altgraph.pth:python/altgraph
|
||||
marionette_driver.pth:testing/marionette/client
|
||||
marionette_harness.pth:testing/marionette/harness
|
||||
browsermobproxy.pth:testing/marionette/harness/marionette_harness/runner/mixins/browsermob-proxy-py
|
||||
|
@ -16,7 +15,6 @@ optional:setup.py:python/psutil:build_ext:--inplace
|
|||
optional:psutil.pth:python/psutil
|
||||
which.pth:python/which
|
||||
ply.pth:other-licenses/ply/
|
||||
macholib.pth:python/macholib
|
||||
mock.pth:python/mock-1.0.0
|
||||
py.pth:python/py
|
||||
pytest.pth:python/pytest
|
||||
|
@ -32,7 +30,6 @@ objdir:build
|
|||
gyp.pth:media/webrtc/trunk/tools/gyp/pylib
|
||||
pyasn1.pth:python/pyasn1
|
||||
pyasn1_modules.pth:python/pyasn1-modules
|
||||
bitstring.pth:python/bitstring
|
||||
redo.pth:python/redo
|
||||
requests.pth:python/requests
|
||||
rsa.pth:python/rsa
|
||||
|
|
|
@ -214,3 +214,78 @@ button {
|
|||
.error-page .error-page-details {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.addon-target-container {
|
||||
background: #fff;
|
||||
box-shadow: 0 0 1px rgba(0, 0, 0, 0.12);
|
||||
list-style-type: none;
|
||||
margin: 0 0 8px;
|
||||
padding: 4px 16px;
|
||||
transition: box-shadow 150ms;
|
||||
}
|
||||
|
||||
.addon-target-container:hover {
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
.addon-target-container .target {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.addon-target-actions {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.addon-target-container .target-icon {
|
||||
margin-inline-end: 16px;
|
||||
}
|
||||
|
||||
.addon-target-container .name {
|
||||
align-self: center;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.addon-target-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #0087ff;
|
||||
font-size: 14px;
|
||||
margin: 12px;
|
||||
min-width: auto;
|
||||
padding: 4px;
|
||||
transition: color 150ms;
|
||||
}
|
||||
|
||||
.addon-target-button:active,
|
||||
.addon-target-button:hover,
|
||||
.addon-target-button:enabled:hover:active {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.addon-target-button:disabled {
|
||||
color: #999;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.addon-target-button:enabled:focus,
|
||||
.addon-target-button:enabled:hover {
|
||||
background: none;
|
||||
color: #0052cc;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.addon-target-button:enabled:hover:active {
|
||||
color: #003399;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.addon-target-button:first-of-type {
|
||||
/* Subtract the start padding so the button is still a bigger click target but
|
||||
* lines up with the icon. */
|
||||
margin-inline-start: -4px;
|
||||
}
|
||||
|
|
|
@ -59,27 +59,29 @@ module.exports = createClass({
|
|||
const canBeReloaded = target.temporarilyInstalled;
|
||||
|
||||
return dom.li(
|
||||
{ className: "target-container", "data-addon-id": target.addonID },
|
||||
dom.img({
|
||||
className: "target-icon",
|
||||
role: "presentation",
|
||||
src: target.icon
|
||||
}),
|
||||
{ className: "addon-target-container", "data-addon-id": target.addonID },
|
||||
dom.div({ className: "target" },
|
||||
dom.div({ className: "target-name", title: target.name }, target.name)
|
||||
dom.img({
|
||||
className: "target-icon",
|
||||
role: "presentation",
|
||||
src: target.icon
|
||||
}),
|
||||
dom.span({ className: "target-name", title: target.name }, target.name)
|
||||
),
|
||||
dom.div({className: "addon-target-actions"},
|
||||
dom.button({
|
||||
className: "debug-button addon-target-button",
|
||||
onClick: this.debug,
|
||||
disabled: debugDisabled,
|
||||
}, Strings.GetStringFromName("debug")),
|
||||
dom.button({
|
||||
className: "reload-button addon-target-button",
|
||||
onClick: this.reload,
|
||||
disabled: !canBeReloaded,
|
||||
title: !canBeReloaded ?
|
||||
Strings.GetStringFromName("reloadDisabledTooltip") : ""
|
||||
}, Strings.GetStringFromName("reload"))
|
||||
),
|
||||
dom.button({
|
||||
className: "debug-button",
|
||||
onClick: this.debug,
|
||||
disabled: debugDisabled,
|
||||
}, Strings.GetStringFromName("debug")),
|
||||
dom.button({
|
||||
className: "reload-button",
|
||||
onClick: this.reload,
|
||||
disabled: !canBeReloaded,
|
||||
title: !canBeReloaded ?
|
||||
Strings.GetStringFromName("reloadDisabledTooltip") : ""
|
||||
}, Strings.GetStringFromName("reload"))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
const Services = require("Services");
|
||||
const { Curl } = require("devtools/client/shared/curl");
|
||||
const { gDevTools } = require("devtools/client/framework/devtools");
|
||||
const Menu = require("devtools/client/framework/menu");
|
||||
const MenuItem = require("devtools/client/framework/menu-item");
|
||||
const { showMenu } = require("devtools/client/netmonitor/src/utils/menu");
|
||||
const FileSaver = require("devtools/client/shared/file-saver");
|
||||
const clipboardHelper = require("devtools/shared/platform/clipboard");
|
||||
const { HarExporter } = require("./har/har-exporter");
|
||||
|
@ -48,66 +47,65 @@ RequestListContextMenu.prototype = {
|
|||
* Since visible attribute only accept boolean value but the method call may
|
||||
* return undefined, we use !! to force convert any object to boolean
|
||||
*/
|
||||
open({ screenX = 0, screenY = 0 } = {}) {
|
||||
open(event = {}) {
|
||||
let selectedRequest = this.selectedRequest;
|
||||
let menu = [];
|
||||
let copySubmenu = [];
|
||||
|
||||
let menu = new Menu();
|
||||
let copySubmenu = new Menu();
|
||||
|
||||
copySubmenu.append(new MenuItem({
|
||||
copySubmenu.push({
|
||||
id: "request-list-context-copy-url",
|
||||
label: L10N.getStr("netmonitor.context.copyUrl"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyUrl.accesskey"),
|
||||
visible: !!selectedRequest,
|
||||
click: () => this.copyUrl(),
|
||||
}));
|
||||
});
|
||||
|
||||
copySubmenu.append(new MenuItem({
|
||||
copySubmenu.push({
|
||||
id: "request-list-context-copy-url-params",
|
||||
label: L10N.getStr("netmonitor.context.copyUrlParams"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyUrlParams.accesskey"),
|
||||
visible: !!(selectedRequest && getUrlQuery(selectedRequest.url)),
|
||||
click: () => this.copyUrlParams(),
|
||||
}));
|
||||
});
|
||||
|
||||
copySubmenu.append(new MenuItem({
|
||||
copySubmenu.push({
|
||||
id: "request-list-context-copy-post-data",
|
||||
label: L10N.getStr("netmonitor.context.copyPostData"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyPostData.accesskey"),
|
||||
visible: !!(selectedRequest && selectedRequest.requestPostData),
|
||||
click: () => this.copyPostData(),
|
||||
}));
|
||||
});
|
||||
|
||||
copySubmenu.append(new MenuItem({
|
||||
copySubmenu.push({
|
||||
id: "request-list-context-copy-as-curl",
|
||||
label: L10N.getStr("netmonitor.context.copyAsCurl"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"),
|
||||
visible: !!selectedRequest,
|
||||
click: () => this.copyAsCurl(),
|
||||
}));
|
||||
});
|
||||
|
||||
copySubmenu.append(new MenuItem({
|
||||
copySubmenu.push({
|
||||
type: "separator",
|
||||
visible: !!selectedRequest,
|
||||
}));
|
||||
});
|
||||
|
||||
copySubmenu.append(new MenuItem({
|
||||
copySubmenu.push({
|
||||
id: "request-list-context-copy-request-headers",
|
||||
label: L10N.getStr("netmonitor.context.copyRequestHeaders"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyRequestHeaders.accesskey"),
|
||||
visible: !!(selectedRequest && selectedRequest.requestHeaders),
|
||||
click: () => this.copyRequestHeaders(),
|
||||
}));
|
||||
});
|
||||
|
||||
copySubmenu.append(new MenuItem({
|
||||
copySubmenu.push({
|
||||
id: "response-list-context-copy-response-headers",
|
||||
label: L10N.getStr("netmonitor.context.copyResponseHeaders"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyResponseHeaders.accesskey"),
|
||||
visible: !!(selectedRequest && selectedRequest.responseHeaders),
|
||||
click: () => this.copyResponseHeaders(),
|
||||
}));
|
||||
});
|
||||
|
||||
copySubmenu.append(new MenuItem({
|
||||
copySubmenu.push({
|
||||
id: "request-list-context-copy-response",
|
||||
label: L10N.getStr("netmonitor.context.copyResponse"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"),
|
||||
|
@ -116,9 +114,9 @@ RequestListContextMenu.prototype = {
|
|||
selectedRequest.responseContent.content.text &&
|
||||
selectedRequest.responseContent.content.text.length !== 0),
|
||||
click: () => this.copyResponse(),
|
||||
}));
|
||||
});
|
||||
|
||||
copySubmenu.append(new MenuItem({
|
||||
copySubmenu.push({
|
||||
id: "request-list-context-copy-image-as-data-uri",
|
||||
label: L10N.getStr("netmonitor.context.copyImageAsDataUri"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyImageAsDataUri.accesskey"),
|
||||
|
@ -126,37 +124,37 @@ RequestListContextMenu.prototype = {
|
|||
selectedRequest.responseContent &&
|
||||
selectedRequest.responseContent.content.mimeType.includes("image/")),
|
||||
click: () => this.copyImageAsDataUri(),
|
||||
}));
|
||||
});
|
||||
|
||||
copySubmenu.append(new MenuItem({
|
||||
copySubmenu.push({
|
||||
type: "separator",
|
||||
visible: !!selectedRequest,
|
||||
}));
|
||||
});
|
||||
|
||||
copySubmenu.append(new MenuItem({
|
||||
copySubmenu.push({
|
||||
id: "request-list-context-copy-all-as-har",
|
||||
label: L10N.getStr("netmonitor.context.copyAllAsHar"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyAllAsHar.accesskey"),
|
||||
visible: this.sortedRequests.size > 0,
|
||||
click: () => this.copyAllAsHar(),
|
||||
}));
|
||||
});
|
||||
|
||||
menu.append(new MenuItem({
|
||||
menu.push({
|
||||
label: L10N.getStr("netmonitor.context.copy"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copy.accesskey"),
|
||||
visible: !!selectedRequest,
|
||||
submenu: copySubmenu,
|
||||
}));
|
||||
});
|
||||
|
||||
menu.append(new MenuItem({
|
||||
menu.push({
|
||||
id: "request-list-context-save-all-as-har",
|
||||
label: L10N.getStr("netmonitor.context.saveAllAsHar"),
|
||||
accesskey: L10N.getStr("netmonitor.context.saveAllAsHar.accesskey"),
|
||||
visible: this.sortedRequests.size > 0,
|
||||
click: () => this.saveAllAsHar(),
|
||||
}));
|
||||
});
|
||||
|
||||
menu.append(new MenuItem({
|
||||
menu.push({
|
||||
id: "request-list-context-save-image-as",
|
||||
label: L10N.getStr("netmonitor.context.saveImageAs"),
|
||||
accesskey: L10N.getStr("netmonitor.context.saveImageAs.accesskey"),
|
||||
|
@ -164,46 +162,44 @@ RequestListContextMenu.prototype = {
|
|||
selectedRequest.responseContent &&
|
||||
selectedRequest.responseContent.content.mimeType.includes("image/")),
|
||||
click: () => this.saveImageAs(),
|
||||
}));
|
||||
});
|
||||
|
||||
menu.append(new MenuItem({
|
||||
menu.push({
|
||||
type: "separator",
|
||||
visible: !!(NetMonitorController.supportsCustomRequest &&
|
||||
selectedRequest && !selectedRequest.isCustom),
|
||||
}));
|
||||
});
|
||||
|
||||
menu.append(new MenuItem({
|
||||
menu.push({
|
||||
id: "request-list-context-resend",
|
||||
label: L10N.getStr("netmonitor.context.editAndResend"),
|
||||
accesskey: L10N.getStr("netmonitor.context.editAndResend.accesskey"),
|
||||
visible: !!(NetMonitorController.supportsCustomRequest &&
|
||||
selectedRequest && !selectedRequest.isCustom),
|
||||
click: this.cloneSelectedRequest,
|
||||
}));
|
||||
});
|
||||
|
||||
menu.append(new MenuItem({
|
||||
menu.push({
|
||||
type: "separator",
|
||||
visible: !!selectedRequest,
|
||||
}));
|
||||
});
|
||||
|
||||
menu.append(new MenuItem({
|
||||
menu.push({
|
||||
id: "request-list-context-newtab",
|
||||
label: L10N.getStr("netmonitor.context.newTab"),
|
||||
accesskey: L10N.getStr("netmonitor.context.newTab.accesskey"),
|
||||
visible: !!selectedRequest,
|
||||
click: () => this.openRequestInTab()
|
||||
}));
|
||||
});
|
||||
|
||||
menu.append(new MenuItem({
|
||||
menu.push({
|
||||
id: "request-list-context-perf",
|
||||
label: L10N.getStr("netmonitor.context.perfTools"),
|
||||
accesskey: L10N.getStr("netmonitor.context.perfTools.accesskey"),
|
||||
visible: !!NetMonitorController.supportsPerfStats,
|
||||
click: () => this.openStatistics(true)
|
||||
}));
|
||||
});
|
||||
|
||||
menu.popup(screenX, screenY, { doc: window.parent.document });
|
||||
return menu;
|
||||
return showMenu(event, menu);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const Menu = require("devtools/client/framework/menu");
|
||||
const MenuItem = require("devtools/client/framework/menu-item");
|
||||
const { showMenu } = require("devtools/client/netmonitor/src/utils/menu");
|
||||
const { HEADERS } = require("./constants");
|
||||
const { L10N } = require("./utils/l10n");
|
||||
|
||||
|
@ -30,12 +29,12 @@ class RequestListHeaderContextMenu {
|
|||
/**
|
||||
* Handle the context menu opening.
|
||||
*/
|
||||
open({ screenX = 0, screenY = 0 } = {}) {
|
||||
let menu = new Menu();
|
||||
open(event = {}) {
|
||||
let menu = [];
|
||||
let onlyOneColumn = this.visibleColumns.length === 1;
|
||||
|
||||
for (let [column, shown] of this.columns) {
|
||||
menu.append(new MenuItem({
|
||||
menu.push({
|
||||
id: `request-list-header-${column}-toggle`,
|
||||
label: L10N.getStr(`netmonitor.toolbar.${stringMap[column] || column}`),
|
||||
type: "checkbox",
|
||||
|
@ -43,19 +42,18 @@ class RequestListHeaderContextMenu {
|
|||
click: () => this.toggleColumn(column),
|
||||
// We don't want to allow hiding the last visible column
|
||||
disabled: onlyOneColumn && shown,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
menu.append(new MenuItem({ type: "separator" }));
|
||||
menu.push({ type: "separator" });
|
||||
|
||||
menu.append(new MenuItem({
|
||||
menu.push({
|
||||
id: "request-list-header-reset-columns",
|
||||
label: L10N.getStr("netmonitor.toolbar.resetColumns"),
|
||||
click: () => this.resetColumns(),
|
||||
}));
|
||||
});
|
||||
|
||||
menu.popup(screenX, screenY, { doc: window.parent.document });
|
||||
return menu;
|
||||
return showMenu(event, menu);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/* 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 Menu = require("devtools/client/framework/menu");
|
||||
const MenuItem = require("devtools/client/framework/menu-item");
|
||||
|
||||
function showMenu(evt, items) {
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let menu = new Menu();
|
||||
items.forEach((item) => {
|
||||
let menuItem = new MenuItem(item);
|
||||
let subItems = item.submenu;
|
||||
|
||||
if (subItems) {
|
||||
let subMenu = new Menu();
|
||||
subItems.forEach((subItem) => {
|
||||
subMenu.append(new MenuItem(subItem));
|
||||
});
|
||||
menuItem.submenu = subMenu;
|
||||
}
|
||||
|
||||
menu.append(menuItem);
|
||||
});
|
||||
|
||||
menu.popup(evt.screenX, evt.screenY, { doc: window.parent.document });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
showMenu,
|
||||
};
|
|
@ -11,6 +11,7 @@ DevToolsModules(
|
|||
'format-utils.js',
|
||||
'l10n.js',
|
||||
'mdn-utils.js',
|
||||
'menu.js',
|
||||
'prefs.js',
|
||||
'request-utils.js',
|
||||
'sort-predicates.js',
|
||||
|
|
|
@ -53,6 +53,7 @@ let webpackConfig = {
|
|||
"devtools/client/framework/menu": "devtools-modules/client/framework/menu",
|
||||
"devtools/client/framework/menu-item": "devtools-modules/client/framework/menu-item",
|
||||
"devtools/client/locales": path.join(__dirname, "../locales/en-US"),
|
||||
"devtools/client/netmonitor/src/utils/menu": "devtools-launchpad/src/components/shared/menu",
|
||||
"devtools/client/shared/components/reps/reps": "devtools-reps",
|
||||
"devtools/client/shared/components/search-box": "devtools-modules/client/shared/components/search-box",
|
||||
"devtools/client/shared/components/splitter/split-box": "devtools-splitter",
|
||||
|
|
|
@ -11,7 +11,7 @@ var gGroundTruthImageData;
|
|||
|
||||
function prepareSources() {
|
||||
gVideo = document.createElement("video");
|
||||
gVideo.src = "http://example.com/tests/dom/canvas/test/crossorigin/video.sjs?name=tests/dom/media/test/320x240.ogv&type=video/ogg&cors=anonymous";
|
||||
gVideo.src = "http://example.com/tests/dom/canvas/test/crossorigin/video.sjs?name=tests/dom/canvas/test/320x240.ogv&type=video/ogg&cors=anonymous";
|
||||
gVideo.crossOrigin = "anonymous";
|
||||
gVideo.autoplay = "true"
|
||||
|
||||
|
|
|
@ -39,6 +39,9 @@ support-files =
|
|||
offscreencanvas_mask.svg
|
||||
offscreencanvas_neuter.js
|
||||
offscreencanvas_serviceworker_inner.html
|
||||
crossorigin/image.png
|
||||
crossorigin/video.sjs
|
||||
../../media/test/320x240.ogv
|
||||
|
||||
[test_2d.clearRect.image.offscreen.html]
|
||||
[test_2d.clip.winding.html]
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<body>
|
||||
|
||||
<img src="image_anim-gr.gif" id="image" class="resource">
|
||||
<video width="320" height="240" src="http://example.com/tests/dom/canvas/test/crossorigin/video.sjs?name=tests/dom/media/test/320x240.ogv&type=video/ogg&cors=anonymous" id="video" crossOrigin="anonymous" autoplay></video>
|
||||
<video width="320" height="240" src="http://example.com/tests/dom/canvas/test/crossorigin/video.sjs?name=tests/dom/canvas/test/320x240.ogv&type=video/ogg&cors=anonymous" id="video" crossOrigin="anonymous" autoplay></video>
|
||||
|
||||
<canvas id="c1" class="output" width="128" height="128"></canvas>
|
||||
<canvas id="c2" width="128" height="128"></canvas>
|
||||
|
@ -226,7 +226,7 @@ function testSecurityErrors() {
|
|||
reject();
|
||||
}
|
||||
|
||||
uncleanVideo.src = "http://example.com/tests/dom/canvas/test/crossorigin/video.sjs?name=tests/dom/media/test/320x240.ogv&type=video/ogg";
|
||||
uncleanVideo.src = "http://example.com/tests/dom/canvas/test/crossorigin/video.sjs?name=tests/dom/canvas/test/320x240.ogv&type=video/ogg";
|
||||
uncleanVideo.play();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@ var gJPEGBlob;
|
|||
|
||||
function prepareSources() {
|
||||
gVideo = document.createElement("video");
|
||||
gVideo.src = "http://example.com/tests/dom/canvas/test/crossorigin/video.sjs?name=tests/dom/media/test/320x240.ogv&type=video/ogg&cors=anonymous";
|
||||
gVideo.src = "http://example.com/tests/dom/canvas/test/crossorigin/video.sjs?name=tests/dom/canvas/test/320x240.ogv&type=video/ogg&cors=anonymous";
|
||||
gVideo.crossOrigin = "anonymous";
|
||||
gVideo.autoplay = "true"
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ fails == 596455-2b.html 596455-2b.html
|
|||
# (Fuzzy necessary due to pixel-wise comparison of different JPEGs.
|
||||
# The vast majority of the fuzziness comes from Linux and WinXP.)
|
||||
== bug917595-iframe-1.html bug917595-iframe-1.html
|
||||
fails == bug917595-exif-rotated.jpg bug917595-exif-rotated.jpg
|
||||
== bug917595-exif-rotated.jpg bug917595-exif-rotated.jpg
|
||||
|
||||
# Test support for SVG-as-image in <picture> elements.
|
||||
== bug1106522-1.html bug1106522-1.html
|
||||
|
|
|
@ -30,6 +30,7 @@ VRMockDisplay::VRMockDisplay(const nsCString& aID, uint32_t aDeviceID)
|
|||
: mDeviceID(aDeviceID)
|
||||
, mTimestamp(TimeStamp::Now())
|
||||
{
|
||||
mSensorState.Clear();
|
||||
mDisplayInfo.mDisplayName = aID;
|
||||
mDisplayInfo.mType = VRDeviceType::Puppet;
|
||||
mDisplayInfo.mIsConnected = true;
|
||||
|
|
|
@ -27,6 +27,7 @@ public:
|
|||
void SetPose(const Nullable<Float32Array>& aPosition, const Nullable<Float32Array>& aLinearVelocity,
|
||||
const Nullable<Float32Array>& aLinearAcceleration, const Nullable<Float32Array>& aOrientation,
|
||||
const Nullable<Float32Array>& aAngularVelocity, const Nullable<Float32Array>& aAngularAcceleration);
|
||||
void SetMountState(bool aIsMounted) { mDisplayInfo.mIsMounted = aIsMounted; }
|
||||
void Update();
|
||||
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
|
@ -84,4 +85,4 @@ private:
|
|||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_VRServiceTest_h_
|
||||
#endif // mozilla_dom_VRServiceTest_h_
|
||||
|
|
|
@ -33,6 +33,10 @@ var SetEyeParameter = function(eye, offsetX, offsetY, offsetZ,
|
|||
downDegree, leftDegree);
|
||||
}
|
||||
|
||||
var SetMountState = function(isMounted) {
|
||||
vrMockDisplay.setMountState(isMounted);
|
||||
}
|
||||
|
||||
var UpdateVRDisplay = function() {
|
||||
vrMockDisplay.update();
|
||||
}
|
||||
|
@ -42,6 +46,7 @@ var API = {
|
|||
SetVRDisplayPose: SetVRDisplayPose,
|
||||
SetEyeResolution: SetEyeResolution,
|
||||
SetEyeParameter: SetEyeParameter,
|
||||
SetMountState: SetMountState,
|
||||
UpdateVRDisplay: UpdateVRDisplay,
|
||||
|
||||
none: false
|
||||
|
|
|
@ -5,7 +5,9 @@ support-files =
|
|||
runVRTest.js
|
||||
WebVRHelpers.js
|
||||
|
||||
[test_vrDisplay_getFrameData.html]
|
||||
[test_vrDisplay_exitPresent.html]
|
||||
[test_vrDisplay_getFrameData.html]
|
||||
[test_vrDisplay_onvrdisplaydeactivate_crosscontent.html]
|
||||
skip-if = true
|
||||
[test_vrDisplay_requestPresent.html]
|
||||
skip-if = true
|
||||
skip-if = true
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>VRDisplay onvrdisplaydeactivate Crosscontent test</title>
|
||||
<meta name="timeout" content="long"/>
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="VRSimulationDriver.js"></script>
|
||||
<script src="WebVRHelpers.js"></script>
|
||||
<script src="requestPresent.js"></script>
|
||||
<script src="runVRTest.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="vrCanvas"></canvas>
|
||||
<script>
|
||||
|
||||
function startTest() {
|
||||
var canvas = document.getElementById("vrCanvas");
|
||||
var iframe1 = document.getElementById("iframe1").contentWindow;
|
||||
var t = async_test("vrdisplaydeactivate crosscontent test");
|
||||
|
||||
window.addEventListener("vrdisplaydeactivate", () => {
|
||||
t.step(() => {
|
||||
assert_true(vrDisplay.isPresenting,
|
||||
"VRDisplay should be still presenting now without being affected by the event.");
|
||||
t.done();
|
||||
});
|
||||
});
|
||||
|
||||
iframe1.addEventListener("vrdisplaydeactivate", () => {
|
||||
t.unreached_func("vrdisplaydeactivate should not be received by other iframe.");
|
||||
});
|
||||
|
||||
promise_test((test) => {
|
||||
return attachVRDisplay(test).then(() => {
|
||||
return promise_test((test) => {
|
||||
return setupVRDisplay(test).then(() => {
|
||||
VRSimulationDriver.SetMountState(true);
|
||||
VRSimulationDriver.UpdateVRDisplay();
|
||||
return WebVRHelpers.RequestPresentOnVRDisplay(vrDisplay, [{source: canvas}]);
|
||||
}).then(() => {
|
||||
VRSimulationDriver.SetMountState(false);
|
||||
VRSimulationDriver.UpdateVRDisplay();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
runVRTest(startTest);
|
||||
</script>
|
||||
<iframe id="iframe1"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -15,6 +15,7 @@ interface VRMockDisplay {
|
|||
void setPose(Float32Array? position, Float32Array? linearVelocity,
|
||||
Float32Array? linearAcceleration, Float32Array? orientation,
|
||||
Float32Array? angularVelocity, Float32Array? angularAcceleration);
|
||||
void setMountState(boolean isMounted);
|
||||
void update();
|
||||
};
|
||||
|
||||
|
|
|
@ -717,13 +717,12 @@ nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallba
|
|||
|
||||
// Helper function that iterates over the list of dictionaries and sets the one
|
||||
// that matches based on a given comparison type.
|
||||
nsresult
|
||||
nsEditorSpellCheck::TryDictionary(const nsAString& aDictName,
|
||||
nsTArray<nsString>& aDictList,
|
||||
enum dictCompare aCompareType)
|
||||
void
|
||||
nsEditorSpellCheck::BuildDictionaryList(const nsAString& aDictName,
|
||||
const nsTArray<nsString>& aDictList,
|
||||
enum dictCompare aCompareType,
|
||||
nsTArray<nsString>& aOutList)
|
||||
{
|
||||
nsresult rv = NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
for (uint32_t i = 0; i < aDictList.Length(); i++) {
|
||||
nsAutoString dictStr(aDictList.ElementAt(i));
|
||||
bool equals = false;
|
||||
|
@ -739,18 +738,18 @@ nsEditorSpellCheck::TryDictionary(const nsAString& aDictName,
|
|||
break;
|
||||
}
|
||||
if (equals) {
|
||||
rv = mSpellChecker->SetCurrentDictionary(dictStr);
|
||||
aOutList.AppendElement(dictStr);
|
||||
#ifdef DEBUG_DICT
|
||||
if (NS_SUCCEEDED(rv))
|
||||
printf("***** Set |%s|.\n", NS_ConvertUTF16toUTF8(dictStr).get());
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
printf("***** Trying |%s|.\n", NS_ConvertUTF16toUTF8(dictStr).get());
|
||||
}
|
||||
#endif
|
||||
// We always break here. We tried to set the dictionary to an existing
|
||||
// dictionary from the list. This must work, if it doesn't, there is
|
||||
// no point trying another one.
|
||||
break;
|
||||
return;
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -759,14 +758,13 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
|
|||
MOZ_ASSERT(aFetcher);
|
||||
RefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
|
||||
|
||||
// Important: declare the holder after the callback caller so that the former
|
||||
// is destructed first so that it's not active when the callback is called.
|
||||
CallbackCaller callbackCaller(aFetcher->mCallback);
|
||||
UpdateDictionaryHolder holder(this);
|
||||
BeginUpdateDictionary();
|
||||
|
||||
if (aFetcher->mGroup < mDictionaryFetcherGroup) {
|
||||
// SetCurrentDictionary was called after the fetch started. Don't overwrite
|
||||
// that dictionary with the fetched one.
|
||||
EndUpdateDictionary();
|
||||
aFetcher->mCallback->EditorSpellCheckDone();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -810,13 +808,14 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
|
|||
#endif
|
||||
}
|
||||
|
||||
// Auxiliary status.
|
||||
nsresult rv2;
|
||||
|
||||
// We obtain a list of available dictionaries.
|
||||
nsTArray<nsString> dictList;
|
||||
rv2 = mSpellChecker->GetDictionaryList(&dictList);
|
||||
NS_ENSURE_SUCCESS(rv2, rv2);
|
||||
AutoTArray<nsString, 8> dictList;
|
||||
nsresult rv = mSpellChecker->GetDictionaryList(&dictList);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
EndUpdateDictionary();
|
||||
aFetcher->mCallback->EditorSpellCheckDone();
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Priority 1:
|
||||
// If we successfully fetched a dictionary from content prefs, do not go
|
||||
|
@ -828,27 +827,61 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
|
|||
if (!(flags & nsIPlaintextEditor::eEditorMailMask)) {
|
||||
dictName.Assign(aFetcher->mDictionary);
|
||||
if (!dictName.IsEmpty()) {
|
||||
if (NS_SUCCEEDED(TryDictionary(dictName, dictList, DICT_NORMAL_COMPARE))) {
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Assigned from content preferences |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(dictName).get());
|
||||
#endif
|
||||
AutoTArray<nsString, 1> tryDictList;
|
||||
BuildDictionaryList(dictName, dictList, DICT_NORMAL_COMPARE, tryDictList);
|
||||
|
||||
// We take an early exit here, so let's not forget to clear the word
|
||||
// list.
|
||||
DeleteSuggestedWordList();
|
||||
return NS_OK;
|
||||
}
|
||||
// May be dictionary was uninstalled ?
|
||||
// Clear the content preference and continue.
|
||||
ClearCurrentDictionary(mEditor);
|
||||
RefPtr<nsEditorSpellCheck> self = this;
|
||||
RefPtr<DictionaryFetcher> fetcher = aFetcher;
|
||||
mSpellChecker->SetCurrentDictionaryFromList(tryDictList)->Then(
|
||||
AbstractThread::MainThread(),
|
||||
__func__,
|
||||
[self, fetcher]() {
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Assigned from content preferences |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(dictName).get());
|
||||
#endif
|
||||
// We take an early exit here, so let's not forget to clear the word
|
||||
// list.
|
||||
self->DeleteSuggestedWordList();
|
||||
|
||||
self->EndUpdateDictionary();
|
||||
fetcher->mCallback->EditorSpellCheckDone();
|
||||
},
|
||||
[self, fetcher]() {
|
||||
// May be dictionary was uninstalled ?
|
||||
// Clear the content preference and continue.
|
||||
ClearCurrentDictionary(self->mEditor);
|
||||
|
||||
// Priority 2 or later will handled by the following
|
||||
self->SetFallbackDictionary(fetcher);
|
||||
});
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
SetFallbackDictionary(aFetcher);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsEditorSpellCheck::SetFallbackDictionary(DictionaryFetcher* aFetcher)
|
||||
{
|
||||
MOZ_ASSERT(mUpdateDictionaryRunning);
|
||||
|
||||
AutoTArray<nsString, 6> tryDictList;
|
||||
|
||||
// We obtain a list of available dictionaries.
|
||||
AutoTArray<nsString, 8> dictList;
|
||||
nsresult rv = mSpellChecker->GetDictionaryList(&dictList);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
EndUpdateDictionary();
|
||||
aFetcher->mCallback->EditorSpellCheckDone();
|
||||
return;
|
||||
}
|
||||
|
||||
// Priority 2:
|
||||
// After checking the content preferences, we use the language of the element
|
||||
// or document.
|
||||
dictName.Assign(mPreferredLang);
|
||||
nsAutoString dictName(mPreferredLang);
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Assigned from element/doc |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(dictName).get());
|
||||
|
@ -858,144 +891,134 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
|
|||
nsAutoString preferredDict;
|
||||
preferredDict = Preferences::GetLocalizedString("spellchecker.dictionary");
|
||||
|
||||
// The following will be driven by this status. Once we were able to set a
|
||||
// dictionary successfully, we're done. So we start with a "failed" status.
|
||||
nsresult rv = NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
if (!dictName.IsEmpty()) {
|
||||
// RFC 5646 explicitly states that matches should be case-insensitive.
|
||||
rv = TryDictionary (dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE);
|
||||
BuildDictionaryList(dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE,
|
||||
tryDictList);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Setting of |%s| failed (or it wasn't available)\n",
|
||||
NS_ConvertUTF16toUTF8(dictName).get());
|
||||
printf("***** Trying from element/doc |%s| \n",
|
||||
NS_ConvertUTF16toUTF8(dictName).get());
|
||||
#endif
|
||||
|
||||
// Required dictionary was not available. Try to get a dictionary
|
||||
// matching at least language part of dictName.
|
||||
nsAutoString langCode;
|
||||
int32_t dashIdx = dictName.FindChar('-');
|
||||
if (dashIdx != -1) {
|
||||
langCode.Assign(Substring(dictName, 0, dashIdx));
|
||||
} else {
|
||||
langCode.Assign(dictName);
|
||||
}
|
||||
|
||||
// Try dictionary.spellchecker preference, if it starts with langCode,
|
||||
// so we don't just get any random dictionary matching the language.
|
||||
if (!preferredDict.IsEmpty() &&
|
||||
nsStyleUtil::DashMatchCompare(preferredDict, langCode, nsDefaultStringComparator())) {
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Trying preference value |%s| since it matches language code\n",
|
||||
NS_ConvertUTF16toUTF8(preferredDict).get());
|
||||
#endif
|
||||
rv = TryDictionary (preferredDict, dictList,
|
||||
DICT_COMPARE_CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
// Use any dictionary with the required language.
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Trying to find match for language code |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(langCode).get());
|
||||
#endif
|
||||
rv = TryDictionary (langCode, dictList, DICT_COMPARE_DASHMATCH);
|
||||
}
|
||||
// Required dictionary was not available. Try to get a dictionary
|
||||
// matching at least language part of dictName.
|
||||
nsAutoString langCode;
|
||||
int32_t dashIdx = dictName.FindChar('-');
|
||||
if (dashIdx != -1) {
|
||||
langCode.Assign(Substring(dictName, 0, dashIdx));
|
||||
} else {
|
||||
langCode.Assign(dictName);
|
||||
}
|
||||
|
||||
// Try dictionary.spellchecker preference, if it starts with langCode,
|
||||
// so we don't just get any random dictionary matching the language.
|
||||
if (!preferredDict.IsEmpty() &&
|
||||
nsStyleUtil::DashMatchCompare(preferredDict, langCode, nsDefaultStringComparator())) {
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Trying preference value |%s| since it matches language code\n",
|
||||
NS_ConvertUTF16toUTF8(preferredDict).get());
|
||||
#endif
|
||||
BuildDictionaryList(preferredDict, dictList,
|
||||
DICT_COMPARE_CASE_INSENSITIVE, tryDictList);
|
||||
}
|
||||
|
||||
// Use any dictionary with the required language.
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Trying to find match for language code |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(langCode).get());
|
||||
#endif
|
||||
BuildDictionaryList(langCode, dictList, DICT_COMPARE_DASHMATCH,
|
||||
tryDictList);
|
||||
}
|
||||
|
||||
// Priority 3:
|
||||
// If the document didn't supply a dictionary or the setting failed,
|
||||
// try the user preference next.
|
||||
if (NS_FAILED(rv)) {
|
||||
if (!preferredDict.IsEmpty()) {
|
||||
if (!preferredDict.IsEmpty()) {
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Trying preference value |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(preferredDict).get());
|
||||
printf("***** Trying preference value |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(preferredDict).get());
|
||||
#endif
|
||||
rv = TryDictionary (preferredDict, dictList, DICT_NORMAL_COMPARE);
|
||||
}
|
||||
BuildDictionaryList(preferredDict, dictList, DICT_NORMAL_COMPARE,
|
||||
tryDictList);
|
||||
}
|
||||
|
||||
// Priority 4:
|
||||
// As next fallback, try the current locale.
|
||||
if (NS_FAILED(rv)) {
|
||||
nsAutoCString utf8DictName;
|
||||
LocaleService::GetInstance()->GetAppLocaleAsLangTag(utf8DictName);
|
||||
nsAutoCString utf8DictName;
|
||||
LocaleService::GetInstance()->GetAppLocaleAsLangTag(utf8DictName);
|
||||
|
||||
dictName.Assign(EmptyString());
|
||||
AppendUTF8toUTF16(utf8DictName, dictName);
|
||||
CopyUTF8toUTF16(utf8DictName, dictName);
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Trying locale |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(dictName).get());
|
||||
printf("***** Trying locale |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(dictName).get());
|
||||
#endif
|
||||
rv = TryDictionary (dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
// Still no success.
|
||||
BuildDictionaryList(dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE,
|
||||
tryDictList);
|
||||
|
||||
// Priority 5:
|
||||
// If we have a current dictionary, don't try anything else.
|
||||
nsAutoString currentDictionary;
|
||||
rv2 = GetCurrentDictionary(currentDictionary);
|
||||
// If we have a current dictionary and we don't have no item in try list,
|
||||
// don't try anything else.
|
||||
nsAutoString currentDictionary;
|
||||
GetCurrentDictionary(currentDictionary);
|
||||
if (!currentDictionary.IsEmpty() && tryDictList.IsEmpty()) {
|
||||
#ifdef DEBUG_DICT
|
||||
if (NS_SUCCEEDED(rv2)) {
|
||||
printf("***** Retrieved current dict |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(currentDictionary).get());
|
||||
printf("***** Retrieved current dict |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(currentDictionary).get());
|
||||
#endif
|
||||
EndUpdateDictionary();
|
||||
aFetcher->mCallback->EditorSpellCheckDone();
|
||||
return;
|
||||
}
|
||||
|
||||
// Priority 6:
|
||||
// Try to get current dictionary from environment variable LANG.
|
||||
// LANG = language[_territory][.charset]
|
||||
char* env_lang = getenv("LANG");
|
||||
if (env_lang) {
|
||||
nsString lang = NS_ConvertUTF8toUTF16(env_lang);
|
||||
// Strip trailing charset, if there is any.
|
||||
int32_t dot_pos = lang.FindChar('.');
|
||||
if (dot_pos != -1) {
|
||||
lang = Substring(lang, 0, dot_pos);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (NS_FAILED(rv2) || currentDictionary.IsEmpty()) {
|
||||
// Priority 6:
|
||||
// Try to get current dictionary from environment variable LANG.
|
||||
// LANG = language[_territory][.charset]
|
||||
char* env_lang = getenv("LANG");
|
||||
if (env_lang) {
|
||||
nsString lang = NS_ConvertUTF8toUTF16(env_lang);
|
||||
// Strip trailing charset, if there is any.
|
||||
int32_t dot_pos = lang.FindChar('.');
|
||||
if (dot_pos != -1) {
|
||||
lang = Substring(lang, 0, dot_pos);
|
||||
}
|
||||
|
||||
int32_t underScore = lang.FindChar('_');
|
||||
if (underScore != -1) {
|
||||
lang.Replace(underScore, 1, '-');
|
||||
int32_t underScore = lang.FindChar('_');
|
||||
if (underScore != -1) {
|
||||
lang.Replace(underScore, 1, '-');
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Trying LANG from environment |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(lang).get());
|
||||
printf("***** Trying LANG from environment |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(lang).get());
|
||||
#endif
|
||||
nsAutoString lang2;
|
||||
lang2.Assign(lang);
|
||||
rv = TryDictionary(lang2, dictList, DICT_COMPARE_CASE_INSENSITIVE);
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 7:
|
||||
// If it does not work, pick the first one.
|
||||
if (NS_FAILED(rv) && !dictList.IsEmpty()) {
|
||||
nsAutoString firstInList;
|
||||
firstInList.Assign(dictList[0]);
|
||||
rv = TryDictionary(firstInList, dictList, DICT_NORMAL_COMPARE);
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Trying first of list |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(dictList[0]).get());
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
printf ("***** Setting worked.\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
BuildDictionaryList(lang, dictList, DICT_COMPARE_CASE_INSENSITIVE,
|
||||
tryDictList);
|
||||
}
|
||||
}
|
||||
|
||||
// If an error was thrown while setting the dictionary, just
|
||||
// fail silently so that the spellchecker dialog is allowed to come
|
||||
// up. The user can manually reset the language to their choice on
|
||||
// the dialog if it is wrong.
|
||||
// Priority 7:
|
||||
// If it does not work, pick the first one.
|
||||
if (!dictList.IsEmpty()) {
|
||||
BuildDictionaryList(dictList[0], dictList, DICT_NORMAL_COMPARE,
|
||||
tryDictList);
|
||||
#ifdef DEBUG_DICT
|
||||
printf("***** Trying first of list |%s|\n",
|
||||
NS_ConvertUTF16toUTF8(dictList[0]).get());
|
||||
#endif
|
||||
}
|
||||
|
||||
DeleteSuggestedWordList();
|
||||
|
||||
return NS_OK;
|
||||
RefPtr<nsEditorSpellCheck> self = this;
|
||||
RefPtr<DictionaryFetcher> fetcher = aFetcher;
|
||||
mSpellChecker->SetCurrentDictionaryFromList(tryDictList)->Then(
|
||||
AbstractThread::MainThread(),
|
||||
__func__,
|
||||
[self, fetcher]() {
|
||||
// If an error was thrown while setting the dictionary, just
|
||||
// fail silently so that the spellchecker dialog is allowed to come
|
||||
// up. The user can manually reset the language to their choice on
|
||||
// the dialog if it is wrong.
|
||||
self->DeleteSuggestedWordList();
|
||||
self->EndUpdateDictionary();
|
||||
fetcher->mCallback->EditorSpellCheckDone();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -70,11 +70,16 @@ protected:
|
|||
|
||||
bool mUpdateDictionaryRunning;
|
||||
|
||||
nsresult TryDictionary(const nsAString& aDictName, nsTArray<nsString>& aDictList,
|
||||
enum dictCompare aCompareType);
|
||||
void BuildDictionaryList(const nsAString& aDictName,
|
||||
const nsTArray<nsString>& aDictList,
|
||||
enum dictCompare aCompareType,
|
||||
nsTArray<nsString>& aTryList);
|
||||
|
||||
nsresult DictionaryFetched(DictionaryFetcher* aFetchState);
|
||||
|
||||
void SetFallbackDictionary(DictionaryFetcher* aFetcher);
|
||||
|
||||
|
||||
public:
|
||||
void BeginUpdateDictionary() { mUpdateDictionaryRunning = true ;}
|
||||
void EndUpdateDictionary() { mUpdateDictionaryRunning = false ;}
|
||||
|
|
|
@ -145,6 +145,7 @@ EditorBase::EditorBase()
|
|||
, mDispatchInputEvent(true)
|
||||
, mIsInEditAction(false)
|
||||
, mHidingCaret(false)
|
||||
, mSpellCheckerDictionaryUpdated(true)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1344,6 +1345,11 @@ EditorBase::SyncRealTimeSpell()
|
|||
GetInlineSpellChecker(enable, getter_AddRefs(spellChecker));
|
||||
|
||||
if (mInlineSpellChecker) {
|
||||
if (!mSpellCheckerDictionaryUpdated && enable) {
|
||||
mInlineSpellChecker->UpdateCurrentDictionary();
|
||||
mSpellCheckerDictionaryUpdated = true;
|
||||
}
|
||||
|
||||
// We might have a mInlineSpellChecker even if there are no dictionaries
|
||||
// available since we don't destroy the mInlineSpellChecker when the last
|
||||
// dictionariy is removed, but in that case spellChecker is null
|
||||
|
@ -5215,8 +5221,10 @@ void
|
|||
EditorBase::OnFocus(nsIDOMEventTarget* aFocusEventTarget)
|
||||
{
|
||||
InitializeSelection(aFocusEventTarget);
|
||||
if (mInlineSpellChecker) {
|
||||
mSpellCheckerDictionaryUpdated = false;
|
||||
if (mInlineSpellChecker && CanEnableSpellCheck()) {
|
||||
mInlineSpellChecker->UpdateCurrentDictionary();
|
||||
mSpellCheckerDictionaryUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1082,6 +1082,8 @@ protected:
|
|||
bool mIsInEditAction;
|
||||
// Whether caret is hidden forcibly.
|
||||
bool mHidingCaret;
|
||||
// Whether spellchecker dictionary is initialized after focused.
|
||||
bool mSpellCheckerDictionaryUpdated;
|
||||
|
||||
friend bool NSCanUnload(nsISupports* serviceMgr);
|
||||
friend class AutoRules;
|
||||
|
|
|
@ -30,32 +30,40 @@ SimpleTest.waitForFocus(function() {
|
|||
div.focus();
|
||||
synthesizeMouseAtCenter(div, {});
|
||||
|
||||
synthesizeKey(" ", {});
|
||||
setTimeout(function() {
|
||||
synthesizeKey("a", {});
|
||||
getSpellChecker().UpdateCurrentDictionary(() => {
|
||||
synthesizeKey(" ", {});
|
||||
setTimeout(function() {
|
||||
synthesizeKey("VK_BACK_SPACE", {});
|
||||
synthesizeKey("a", {});
|
||||
setTimeout(function() {
|
||||
synthesizeKey("VK_BACK_SPACE", {});
|
||||
|
||||
var sel = getSpellCheckSelection();
|
||||
is(sel.rangeCount, 2, "We should have two misspelled words");
|
||||
is(String(sel.getRangeAt(0)), "fivee", "Correct misspelled word");
|
||||
is(String(sel.getRangeAt(1)), "sixx", "Correct misspelled word");
|
||||
var sel = getSpellCheckSelection();
|
||||
is(sel.rangeCount, 2, "We should have two misspelled words");
|
||||
is(String(sel.getRangeAt(0)), "fivee", "Correct misspelled word");
|
||||
is(String(sel.getRangeAt(1)), "sixx", "Correct misspelled word");
|
||||
|
||||
SimpleTest.finish();
|
||||
SimpleTest.finish();
|
||||
},0);
|
||||
},0);
|
||||
},0);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
function getSpellCheckSelection() {
|
||||
function getEditor() {
|
||||
var Ci = SpecialPowers.Ci;
|
||||
var editingSession = SpecialPowers.wrap(window)
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIEditingSession);
|
||||
var editor = editingSession.getEditorForWindow(window);
|
||||
var selcon = editor.selectionController;
|
||||
return editingSession.getEditorForWindow(window);
|
||||
}
|
||||
|
||||
function getSpellChecker() {
|
||||
return getEditor().getInlineSpellChecker(false).spellChecker;
|
||||
}
|
||||
|
||||
function getSpellCheckSelection() {
|
||||
var selcon = getEditor().selectionController;
|
||||
return selcon.getSelection(selcon.SELECTION_SPELLCHECK);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,37 +27,45 @@ SimpleTest.waitForExplicitFinish();
|
|||
SimpleTest.waitForFocus(function() {
|
||||
var div = document.getElementById("content");
|
||||
div.focus();
|
||||
synthesizeMouseAtCenter(div, {});
|
||||
synthesizeKey("VK_LEFT", {});
|
||||
synthesizeKey("VK_LEFT", {});
|
||||
getSpellChecker().UpdateCurrentDictionary(() => {
|
||||
synthesizeMouseAtCenter(div, {});
|
||||
synthesizeKey("VK_LEFT", {});
|
||||
synthesizeKey("VK_LEFT", {});
|
||||
|
||||
setTimeout(function() {
|
||||
synthesizeKey("VK_BACK_SPACE", {});
|
||||
setTimeout(function() {
|
||||
synthesizeKey(" ", {});
|
||||
|
||||
synthesizeKey("VK_BACK_SPACE", {});
|
||||
setTimeout(function() {
|
||||
var sel = getSpellCheckSelection();
|
||||
is(sel.rangeCount, 2, "We should have two misspelled words");
|
||||
is(String(sel.getRangeAt(0)), "thiss", "Correct misspelled word");
|
||||
is(String(sel.getRangeAt(1)), "onee", "Correct misspelled word");
|
||||
synthesizeKey(" ", {});
|
||||
|
||||
SimpleTest.finish();
|
||||
setTimeout(function() {
|
||||
var sel = getSpellCheckSelection();
|
||||
is(sel.rangeCount, 2, "We should have two misspelled words");
|
||||
is(String(sel.getRangeAt(0)), "thiss", "Correct misspelled word");
|
||||
is(String(sel.getRangeAt(1)), "onee", "Correct misspelled word");
|
||||
|
||||
SimpleTest.finish();
|
||||
},0);
|
||||
},0);
|
||||
},0);
|
||||
},0);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
function getSpellCheckSelection() {
|
||||
function getEditor() {
|
||||
var Ci = SpecialPowers.Ci;
|
||||
var editingSession = SpecialPowers.wrap(window)
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIEditingSession);
|
||||
var editor = editingSession.getEditorForWindow(window);
|
||||
var selcon = editor.selectionController;
|
||||
return editingSession.getEditorForWindow(window);
|
||||
}
|
||||
|
||||
function getSpellChecker() {
|
||||
return getEditor().getInlineSpellChecker(false).spellChecker;
|
||||
}
|
||||
|
||||
function getSpellCheckSelection() {
|
||||
var selcon = getEditor().selectionController;
|
||||
return selcon.getSelection(selcon.SELECTION_SPELLCHECK);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#ifndef nsISpellChecker_h__
|
||||
#define nsISpellChecker_h__
|
||||
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "nsISupports.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
|
@ -114,6 +115,13 @@ public:
|
|||
* empty string, spellchecker will be disabled.
|
||||
*/
|
||||
NS_IMETHOD SetCurrentDictionary(const nsAString &aDictionary) = 0;
|
||||
|
||||
/**
|
||||
* Tells the spellchecker to use a specific dictionary from list.
|
||||
* @param aList a preferred dictionary list
|
||||
*/
|
||||
NS_IMETHOD_(RefPtr<mozilla::GenericPromise>)
|
||||
SetCurrentDictionaryFromList(const nsTArray<nsString>& aList) = 0;
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(nsISpellChecker, NS_ISPELLCHECKER_IID)
|
||||
|
|
|
@ -17,6 +17,11 @@ parent:
|
|||
sync CheckAndSuggest(nsString aWord) returns (bool aIsMisspelled, nsString[] aSuggestions);
|
||||
|
||||
sync SetDictionary(nsString aDictionary) returns (bool success);
|
||||
|
||||
async SetDictionaryFromList(nsString[] aList, intptr_t aPromiseId);
|
||||
|
||||
child:
|
||||
async NotifyOfCurrentDictionary(nsString aDictionary, intptr_t aPromiseId);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* 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/. */
|
||||
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "RemoteSpellCheckEngineChild.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -18,4 +19,37 @@ RemoteSpellcheckEngineChild::~RemoteSpellcheckEngineChild()
|
|||
mOwner->DeleteRemoteEngine();
|
||||
}
|
||||
|
||||
RefPtr<GenericPromise>
|
||||
RemoteSpellcheckEngineChild::SetCurrentDictionaryFromList(
|
||||
const nsTArray<nsString>& aList)
|
||||
{
|
||||
MozPromiseHolder<GenericPromise>* promiseHolder =
|
||||
new MozPromiseHolder<GenericPromise>();
|
||||
if (!SendSetDictionaryFromList(
|
||||
aList,
|
||||
reinterpret_cast<intptr_t>(promiseHolder))) {
|
||||
delete promiseHolder;
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
// promiseHolder will removed by receive message
|
||||
return promiseHolder->Ensure(__func__);
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RemoteSpellcheckEngineChild::RecvNotifyOfCurrentDictionary(
|
||||
const nsString& aDictionary,
|
||||
const intptr_t& aId)
|
||||
{
|
||||
MozPromiseHolder<GenericPromise>* promiseHolder =
|
||||
reinterpret_cast<MozPromiseHolder<GenericPromise>*>(aId);
|
||||
mOwner->mCurrentDictionary = aDictionary;
|
||||
if (aDictionary.IsEmpty()) {
|
||||
promiseHolder->RejectIfExists(NS_ERROR_NOT_AVAILABLE, __func__);
|
||||
} else {
|
||||
promiseHolder->ResolveIfExists(true, __func__);
|
||||
}
|
||||
delete promiseHolder;
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
} //namespace mozilla
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#ifndef RemoteSpellcheckEngineChild_h_
|
||||
#define RemoteSpellcheckEngineChild_h_
|
||||
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/PRemoteSpellcheckEngineChild.h"
|
||||
#include "mozSpellChecker.h"
|
||||
|
||||
|
@ -18,6 +19,13 @@ public:
|
|||
explicit RemoteSpellcheckEngineChild(mozSpellChecker *aOwner);
|
||||
virtual ~RemoteSpellcheckEngineChild();
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvNotifyOfCurrentDictionary(
|
||||
const nsString& aDictionary,
|
||||
const intptr_t& aPromiseId) override;
|
||||
|
||||
RefPtr<GenericPromise> SetCurrentDictionaryFromList(
|
||||
const nsTArray<nsString>& aList);
|
||||
|
||||
private:
|
||||
mozSpellChecker *mOwner;
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "RemoteSpellCheckEngineParent.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "nsISpellChecker.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
|
||||
|
@ -28,6 +29,23 @@ RemoteSpellcheckEngineParent::RecvSetDictionary(
|
|||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RemoteSpellcheckEngineParent::RecvSetDictionaryFromList(
|
||||
nsTArray<nsString>&& aList,
|
||||
const intptr_t& aPromiseId)
|
||||
{
|
||||
for (auto& dictionary : aList) {
|
||||
MOZ_ASSERT(!dictionary.IsEmpty());
|
||||
nsresult rv = mSpellChecker->SetCurrentDictionary(dictionary);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
Unused << SendNotifyOfCurrentDictionary(dictionary, aPromiseId);
|
||||
return IPC_OK();
|
||||
}
|
||||
}
|
||||
Unused << SendNotifyOfCurrentDictionary(EmptyString(), aPromiseId);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RemoteSpellcheckEngineParent::RecvCheck(
|
||||
const nsString& aWord,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "mozilla/PRemoteSpellcheckEngineParent.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
class nsISpellChecker;
|
||||
|
||||
|
@ -23,6 +24,10 @@ public:
|
|||
virtual mozilla::ipc::IPCResult RecvSetDictionary(const nsString& aDictionary,
|
||||
bool* success) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvSetDictionaryFromList(
|
||||
nsTArray<nsString>&& aList,
|
||||
const intptr_t& aPromiseId) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvCheck(const nsString& aWord, bool* aIsMisspelled) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvCheckAndSuggest(const nsString& aWord,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "nsXULAppAPI.h"
|
||||
|
||||
using mozilla::dom::ContentChild;
|
||||
using mozilla::GenericPromise;
|
||||
using mozilla::PRemoteSpellcheckEngineChild;
|
||||
using mozilla::RemoteSpellcheckEngineChild;
|
||||
|
||||
|
@ -429,6 +430,28 @@ mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary)
|
|||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(RefPtr<GenericPromise>)
|
||||
mozSpellChecker::SetCurrentDictionaryFromList(const nsTArray<nsString>& aList)
|
||||
{
|
||||
if (aList.IsEmpty()) {
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_INVALID_ARG, __func__);
|
||||
}
|
||||
|
||||
if (XRE_IsContentProcess()) {
|
||||
// mCurrentDictionary will be set by RemoteSpellCheckEngineChild
|
||||
return mEngine->SetCurrentDictionaryFromList(aList);
|
||||
}
|
||||
|
||||
for (auto& dictionary : aList) {
|
||||
nsresult rv = SetCurrentDictionary(dictionary);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
return GenericPromise::CreateAndResolve(true, __func__);
|
||||
}
|
||||
}
|
||||
// We could not find any engine with the requested dictionary
|
||||
return GenericPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
|
||||
}
|
||||
|
||||
nsresult
|
||||
mozSpellChecker::SetupDoc(int32_t *outBlockOffset)
|
||||
{
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#include "RemoteSpellCheckEngineChild.h"
|
||||
|
||||
namespace mozilla {
|
||||
class PRemoteSpellcheckEngineChild;
|
||||
class RemoteSpellcheckEngineChild;
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -48,6 +47,8 @@ public:
|
|||
NS_IMETHOD GetDictionaryList(nsTArray<nsString> *aDictionaryList) override;
|
||||
NS_IMETHOD GetCurrentDictionary(nsAString &aDictionary) override;
|
||||
NS_IMETHOD SetCurrentDictionary(const nsAString &aDictionary) override;
|
||||
NS_IMETHOD_(RefPtr<mozilla::GenericPromise>)
|
||||
SetCurrentDictionaryFromList(const nsTArray<nsString>& aList) override;
|
||||
|
||||
void DeleteRemoteEngine() {
|
||||
mEngine = nullptr;
|
||||
|
@ -71,6 +72,8 @@ protected:
|
|||
|
||||
nsresult GetEngineList(nsCOMArray<mozISpellCheckingEngine> *aDictionaryList);
|
||||
|
||||
mozilla::PRemoteSpellcheckEngineChild *mEngine;
|
||||
mozilla::RemoteSpellcheckEngineChild *mEngine;
|
||||
|
||||
friend class mozilla::RemoteSpellcheckEngineChild;
|
||||
};
|
||||
#endif // mozSpellChecker_h__
|
||||
|
|
|
@ -319,6 +319,10 @@ static_library("libANGLE") {
|
|||
defines += [ "ANGLE_PRELOADED_D3DCOMPILER_MODULE_NAMES={ " + "\"d3dcompiler_47.dll\", \"d3dcompiler_46.dll\", \"d3dcompiler_43.dll\" }" ]
|
||||
}
|
||||
|
||||
if (angle_enable_hlsl) {
|
||||
sources += rebase_path(gles_gypi.libangle_d3d_hlsl_sources, ".", "src")
|
||||
}
|
||||
|
||||
if (angle_enable_d3d9) {
|
||||
sources += rebase_path(gles_gypi.libangle_d3d9_sources, ".", "src")
|
||||
libs += [ "d3d9.lib" ]
|
||||
|
|
|
@ -69,6 +69,8 @@ Intel Corporation
|
|||
Jie Chen
|
||||
Qiankun Miao
|
||||
Bryan Bernhart
|
||||
Yunchao He
|
||||
Xinghua Cao
|
||||
|
||||
Klarälvdalens Datakonsult AB
|
||||
Milian Wolff
|
||||
|
|
|
@ -17,7 +17,7 @@ deps = {
|
|||
'https://android.googlesource.com/platform/external/cherry' + '@' + 'd2e26b4d864ec2a6757e7f1174e464949ca5bf73',
|
||||
|
||||
'third_party/deqp/src':
|
||||
'https://android.googlesource.com/platform/external/deqp' + '@' + 'f4f3d8079e7a37d7675ab93583e6438d0bca0e58',
|
||||
'https://android.googlesource.com/platform/external/deqp' + '@' + '455d82c60b096e7bd83b6a2f5ed70c61e4bfa759',
|
||||
|
||||
'third_party/libpng':
|
||||
'https://android.googlesource.com/platform/external/libpng' + '@' + '094e181e79a3d6c23fd005679025058b7df1ad6c',
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
vars = {
|
||||
'android_git': 'https://android.googlesource.com',
|
||||
'deqp_revision': 'f4f3d8079e7a37d7675ab93583e6438d0bca0e58',
|
||||
'deqp_revision': '455d82c60b096e7bd83b6a2f5ed70c61e4bfa759',
|
||||
}
|
||||
|
||||
deps_os = {
|
||||
|
|
|
@ -9,6 +9,10 @@ ifdef MOZ_D3DCOMPILER_VISTA_DLL_PATH
|
|||
cp -fp "$(MOZ_D3DCOMPILER_VISTA_DLL_PATH)" "$(DIST)/bin"
|
||||
endif
|
||||
|
||||
ifdef MOZ_D3DCOMPILER_XP_CAB
|
||||
expand '$(MOZ_D3DCOMPILER_XP_CAB)' -F:$(MOZ_D3DCOMPILER_XP_DLL) '$(DIST)/bin'
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
|
|
@ -821,11 +821,13 @@ GL_APICALL void GL_APIENTRY glBlitFramebufferANGLE (GLint srcX0, GLint srcY0, GL
|
|||
#endif
|
||||
#endif /* GL_ANGLE_framebuffer_blit */
|
||||
|
||||
#ifndef GL_ANGLE_webgl_compatibility
|
||||
#define GL_ANGLE_webgl_compatibility 1
|
||||
typedef GLboolean(GL_APIENTRYP PFNGLENABLEEXTENSIONANGLEPROC) (const GLchar *name);
|
||||
#ifndef GL_ANGLE_request_extension
|
||||
#define GL_ANGLE_request_extension 1
|
||||
#define GL_REQUESTABLE_EXTENSIONS_ANGLE 0x93A8
|
||||
#define GL_NUM_REQUESTABLE_EXTENSIONS_ANGLE 0x93A8
|
||||
typedef GLboolean(GL_APIENTRYP PFNGLREQUESTEXTENSIONANGLEPROC) (const GLchar *name);
|
||||
#ifdef GL_GLEXT_PROTOTYPES
|
||||
GL_APICALL GLboolean GL_APIENTRY glEnableExtensionANGLE (const GLchar *name);
|
||||
GL_APICALL GLboolean GL_APIENTRY glRequestExtensionANGLE (const GLchar *name);
|
||||
#endif
|
||||
#endif /* GL_ANGLE_webgl_compatibility */
|
||||
|
||||
|
|
|
@ -20,19 +20,12 @@
|
|||
// and the shading language compiler.
|
||||
//
|
||||
|
||||
namespace sh
|
||||
{
|
||||
// GLenum alias
|
||||
typedef unsigned int GLenum;
|
||||
}
|
||||
|
||||
// Must be included after GLenum proxy typedef
|
||||
// Note: make sure to increment ANGLE_SH_VERSION when changing ShaderVars.h
|
||||
#include "ShaderVars.h"
|
||||
|
||||
// Version number for shader translation API.
|
||||
// It is incremented every time the API changes.
|
||||
#define ANGLE_SH_VERSION 167
|
||||
#define ANGLE_SH_VERSION 168
|
||||
|
||||
enum ShShaderSpec
|
||||
{
|
||||
|
@ -72,8 +65,8 @@ enum ShShaderOutput
|
|||
};
|
||||
|
||||
// Compile options.
|
||||
|
||||
using ShCompileOptions = uint64_t;
|
||||
// The Compile options type is defined in ShaderVars.h, to allow ANGLE to import the ShaderVars
|
||||
// header without needing the ShaderLang header. This avoids some conflicts with glslang.
|
||||
|
||||
const ShCompileOptions SH_VALIDATE = 0;
|
||||
const ShCompileOptions SH_VALIDATE_LOOP_INDEXING = UINT64_C(1) << 0;
|
||||
|
@ -205,7 +198,12 @@ const ShCompileOptions SH_DONT_REMOVE_INVARIANT_FOR_FRAGMENT_INPUT = UINT64_C(1)
|
|||
// Due to spec difference between GLSL 4.1 or lower and ESSL3, some platforms (for example, Mac OSX
|
||||
// core profile) require a variable's "invariant"/"centroid" qualifiers to match between vertex and
|
||||
// fragment shader. A simple solution to allow such shaders to link is to omit the two qualifiers.
|
||||
// Note that the two flags only take effect on ESSL3 input shaders translated to GLSL 4.1 or lower.
|
||||
// AMD driver in Linux requires invariant qualifier to match between vertex and fragment shaders,
|
||||
// while ESSL3 disallows invariant qualifier in fragment shader and GLSL >= 4.2 doesn't require
|
||||
// invariant qualifier to match between shaders. Remove invariant qualifier from vertex shader to
|
||||
// workaround AMD driver bug.
|
||||
// Note that the two flags take effect on ESSL3 input shaders translated to GLSL 4.1 or lower and to
|
||||
// GLSL 4.2 or newer on Linux AMD.
|
||||
// TODO(zmo): This is not a good long-term solution. Simply dropping these qualifiers may break some
|
||||
// developers' content. A more complex workaround of dynamically generating, compiling, and
|
||||
// re-linking shaders that use these qualifiers should be implemented.
|
||||
|
|
|
@ -14,11 +14,13 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Assume ShaderLang.h is included before ShaderVars.h, for sh::GLenum
|
||||
// Note: make sure to increment ANGLE_SH_VERSION when changing ShaderVars.h
|
||||
// This type is defined here to simplify ANGLE's integration with glslang for SPIRv.
|
||||
using ShCompileOptions = uint64_t;
|
||||
|
||||
namespace sh
|
||||
{
|
||||
// GLenum alias
|
||||
typedef unsigned int GLenum;
|
||||
|
||||
// Varying interpolation qualifier, see section 4.3.9 of the ESSL 3.00.4 spec
|
||||
enum InterpolationType
|
||||
|
|
|
@ -139,6 +139,9 @@ if CONFIG['GNU_CXX']:
|
|||
'-Wno-shadow-local',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_DIRECTX_SDK_PATH'] and not CONFIG['MOZ_HAS_WINSDK_WITH_D3D']:
|
||||
LOCAL_INCLUDES += ['%' + '%s/include/' % CONFIG['MOZ_DIRECTX_SDK_PATH']]
|
||||
|
||||
DEFINES['_CRT_SECURE_NO_DEPRECATE'] = True
|
||||
DEFINES['_HAS_EXCEPTIONS'] = 0
|
||||
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
#define ANGLE_COMMIT_HASH "2a250c8a0e15"
|
||||
//
|
||||
// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// commit.h:
|
||||
// This is a default commit hash header, when git is not available.
|
||||
//
|
||||
|
||||
#define ANGLE_COMMIT_HASH "unknown hash"
|
||||
#define ANGLE_COMMIT_HASH_SIZE 12
|
||||
#define ANGLE_COMMIT_DATE "2016-11-23 17:58:16 +0800"
|
||||
#define ANGLE_COMMIT_DATE "unknown date"
|
||||
|
||||
#define ANGLE_DISABLE_PROGRAM_BINARY_LOAD
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче