mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 04:15:43 +00:00
Merge m-c to inbound, a=merge
MozReview-Commit-ID: 5AQXGbI0ke2
This commit is contained in:
commit
e80ed17c41
@ -46,7 +46,7 @@ pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/%LOCAL
|
||||
pref("extensions.getAddons.getWithPerformance.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%");
|
||||
pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%&platform=%OS%&appver=%VERSION%");
|
||||
pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%/%COMPATIBILITY_MODE%?src=firefox");
|
||||
pref("extensions.webservice.discoverURL", "https://services.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%/%COMPATIBILITY_MODE%");
|
||||
pref("extensions.webservice.discoverURL", "https://discovery.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%/%COMPATIBILITY_MODE%");
|
||||
pref("extensions.getAddons.recommended.url", "https://services.addons.mozilla.org/%LOCALE%/%APP%/api/%API_VERSION%/list/recommended/all/%MAX_RESULTS%/%OS%/%VERSION%?src=firefox");
|
||||
pref("extensions.getAddons.link.url", "https://addons.mozilla.org/%LOCALE%/firefox/");
|
||||
|
||||
@ -68,6 +68,12 @@ pref("services.kinto.changes.path", "/buckets/monitor/collections/changes/record
|
||||
pref("services.kinto.bucket", "blocklists");
|
||||
pref("services.kinto.onecrl.collection", "certificates");
|
||||
pref("services.kinto.onecrl.checked", 0);
|
||||
pref("services.kinto.addons.collection", "addons");
|
||||
pref("services.kinto.addons.checked", 0);
|
||||
pref("services.kinto.plugins.collection", "plugins");
|
||||
pref("services.kinto.plugins.checked", 0);
|
||||
pref("services.kinto.gfx.collection", "gfx");
|
||||
pref("services.kinto.gfx.checked", 0);
|
||||
|
||||
// for now, let's keep kinto update out of the release channel
|
||||
#ifdef RELEASE_BUILD
|
||||
|
@ -7,6 +7,7 @@
|
||||
@namespace svg url("http://www.w3.org/2000/svg");
|
||||
|
||||
:root {
|
||||
--identity-popup-expander-width: 38px;
|
||||
--panelui-subview-transition-duration: 150ms;
|
||||
}
|
||||
|
||||
|
@ -6487,10 +6487,13 @@ var gIdentityHandler = {
|
||||
delete this._identityBox;
|
||||
return this._identityBox = document.getElementById("identity-box");
|
||||
},
|
||||
get _identityPopupContentHost () {
|
||||
delete this._identityPopupContentHost;
|
||||
return this._identityPopupContentHost =
|
||||
document.getElementById("identity-popup-content-host");
|
||||
get _identityPopupContentHosts () {
|
||||
delete this._identityPopupContentHosts;
|
||||
return this._identityPopupContentHosts = [...document.querySelectorAll(".identity-popup-headline.host")];
|
||||
},
|
||||
get _identityPopupContentHostless () {
|
||||
delete this._identityPopupContentHostless;
|
||||
return this._identityPopupContentHostless = [...document.querySelectorAll(".identity-popup-headline.hostless")];
|
||||
},
|
||||
get _identityPopupContentOwner () {
|
||||
delete this._identityPopupContentOwner;
|
||||
@ -6933,7 +6936,7 @@ var gIdentityHandler = {
|
||||
let verifier = "";
|
||||
let host = "";
|
||||
let owner = "";
|
||||
let crop = "start";
|
||||
let hostless = false;
|
||||
|
||||
try {
|
||||
host = this.getEffectiveHost();
|
||||
@ -6946,7 +6949,7 @@ var gIdentityHandler = {
|
||||
host = this._uri.specIgnoringRef;
|
||||
// Special URIs without a host (eg, about:) should crop the end so
|
||||
// the protocol can be seen.
|
||||
crop = "end";
|
||||
hostless = true;
|
||||
}
|
||||
|
||||
// Fill in the CA name if we have a valid TLS certificate.
|
||||
@ -6956,8 +6959,6 @@ var gIdentityHandler = {
|
||||
|
||||
// Fill in organization information if we have a valid EV certificate.
|
||||
if (this._isEV) {
|
||||
crop = "end";
|
||||
|
||||
let iData = this.getIdentityData();
|
||||
host = owner = iData.subjectOrg;
|
||||
verifier = this._identityBox.tooltipText;
|
||||
@ -6974,11 +6975,15 @@ var gIdentityHandler = {
|
||||
supplemental += iData.country;
|
||||
}
|
||||
|
||||
// Push the appropriate strings out to the UI. Need to use |value| for the
|
||||
// host as it's a <label> that will be cropped if too long. Using
|
||||
// |textContent| would simply wrap the value.
|
||||
this._identityPopupContentHost.setAttribute("crop", crop);
|
||||
this._identityPopupContentHost.setAttribute("value", host);
|
||||
// Push the appropriate strings out to the UI.
|
||||
this._identityPopupContentHosts.forEach((el) => {
|
||||
el.textContent = host;
|
||||
el.hidden = hostless;
|
||||
});
|
||||
this._identityPopupContentHostless.forEach((el) => {
|
||||
el.setAttribute("value", host);
|
||||
el.hidden = !hostless;
|
||||
});
|
||||
this._identityPopupContentOwner.textContent = owner;
|
||||
this._identityPopupContentSupp.textContent = supplemental;
|
||||
this._identityPopupContentVerif.textContent = verifier;
|
||||
|
@ -854,7 +854,7 @@
|
||||
<menuitem label="&recentBookmarks.label;"
|
||||
id="BMB_recentBookmarks"
|
||||
disabled="true"
|
||||
class="subviewbutton"/>
|
||||
class="menuitem-iconic subviewbutton"/>
|
||||
<menuseparator/>
|
||||
<menu id="BMB_bookmarksToolbar"
|
||||
class="menu-iconic bookmark-item subviewbutton"
|
||||
|
@ -26,7 +26,7 @@
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="menu_HelpPopup_reportPhishingErrortoolmenu"
|
||||
label="&safeb.palm.notdeceptive.label;"
|
||||
accesskey="&reportDeceptiveSiteMenu.accesskey;"
|
||||
accesskey="&safeb.palm.notdeceptive.accesskey;"
|
||||
insertbefore="aboutSeparator"
|
||||
observes="reportPhishingErrorBroadcaster"
|
||||
oncommand="openUILinkIn(gSafeBrowsing.getReportURL('PhishMistake'), 'tab');"
|
||||
|
@ -21,6 +21,7 @@ support-files =
|
||||
browser_tab_dragdrop2_frame1.xul
|
||||
browser_web_channel.html
|
||||
browser_web_channel_iframe.html
|
||||
bug1262648_string_with_newlines.dtd
|
||||
bug592338.html
|
||||
bug792517-2.html
|
||||
bug792517.html
|
||||
|
@ -127,19 +127,32 @@ add_task(function* checkAllTheProperties() {
|
||||
}
|
||||
});
|
||||
|
||||
var checkDTD = Task.async(function* (aURISpec) {
|
||||
let rawContents = yield fetchFile(aURISpec);
|
||||
// The regular expression below is adapted from:
|
||||
// https://hg.mozilla.org/mozilla-central/file/68c0b7d6f16ce5bb023e08050102b5f2fe4aacd8/python/compare-locales/compare_locales/parser.py#l233
|
||||
let entities = rawContents.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/g);
|
||||
for (let entity of entities) {
|
||||
let [, key, str] = entity.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/);
|
||||
// The matched string includes the enclosing quotation marks,
|
||||
// we need to slice them off.
|
||||
str = str.slice(1, -1);
|
||||
testForErrors(aURISpec, key, str);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* checkAllTheDTDs() {
|
||||
let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
|
||||
let uris = yield generateURIsFromDirTree(appDir, [".dtd"]);
|
||||
ok(uris.length, `Found ${uris.length} .dtd files to scan for misused characters`);
|
||||
|
||||
for (let uri of uris) {
|
||||
let rawContents = yield fetchFile(uri.spec);
|
||||
let entities = rawContents.match(/ENTITY\s+([\w\.]*)\s+["'](.*)["']/g);
|
||||
for (let entity of entities) {
|
||||
let [, key, str] = entity.match(/ENTITY\s+([\w\.]*)\s+["'](.*)["']/);
|
||||
testForErrors(uri.spec, key, str);
|
||||
}
|
||||
yield checkDTD(uri.spec);
|
||||
}
|
||||
|
||||
// This support DTD file supplies a string with a newline to make sure
|
||||
// the regex in checkDTD works correctly for that case.
|
||||
let dtdLocation = gTestPath.replace(/\/[^\/]*$/i, "/bug1262648_string_with_newlines.dtd");
|
||||
yield checkDTD(dtdLocation);
|
||||
});
|
||||
|
||||
add_task(function* ensureWhiteListIsEmpty() {
|
||||
|
@ -2,19 +2,18 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", null);
|
||||
function createTemporarySaveDirectory() {
|
||||
var saveDir = Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIProperties)
|
||||
.get("TmpD", Ci.nsIFile);
|
||||
saveDir.append("testsavedir");
|
||||
if (!saveDir.exists())
|
||||
saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
|
||||
return saveDir;
|
||||
}
|
||||
|
||||
function test() {
|
||||
// initialization
|
||||
waitForExplicitFinish();
|
||||
let windowsToClose = [];
|
||||
let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.html";
|
||||
let fileName;
|
||||
let MockFilePicker = SpecialPowers.MockFilePicker;
|
||||
let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
|
||||
.getService(Ci.nsICacheStorageService);
|
||||
|
||||
function checkDiskCacheFor(filename, goon) {
|
||||
function promiseNoCacheEntry(filename) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Visitor.prototype = {
|
||||
onCacheStorageInfo: function(num, consumption)
|
||||
{
|
||||
@ -22,118 +21,95 @@ function test() {
|
||||
},
|
||||
onCacheEntryInfo: function(uri)
|
||||
{
|
||||
var urispec = uri.asciiSpec;
|
||||
let urispec = uri.asciiSpec;
|
||||
info(urispec);
|
||||
is(urispec.includes(filename), false, "web content present in disk cache");
|
||||
},
|
||||
onCacheEntryVisitCompleted: function()
|
||||
{
|
||||
goon();
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
function Visitor() {}
|
||||
|
||||
var storage = cache.diskCacheStorage(LoadContextInfo.default, false);
|
||||
let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
|
||||
.getService(Ci.nsICacheStorageService);
|
||||
let {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", null);
|
||||
let storage = cache.diskCacheStorage(LoadContextInfo.default, false);
|
||||
storage.asyncVisitStorage(new Visitor(), true /* Do walk entries */);
|
||||
}
|
||||
|
||||
function onTransferComplete(downloadSuccess) {
|
||||
ok(downloadSuccess, "Image file should have been downloaded successfully");
|
||||
|
||||
// Give the request a chance to finish and create a cache entry
|
||||
executeSoon(function() {
|
||||
checkDiskCacheFor(fileName, finish);
|
||||
mockTransferCallback = null;
|
||||
});
|
||||
}
|
||||
|
||||
function createTemporarySaveDirectory() {
|
||||
var saveDir = Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIProperties)
|
||||
.get("TmpD", Ci.nsIFile);
|
||||
saveDir.append("testsavedir");
|
||||
if (!saveDir.exists())
|
||||
saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
|
||||
return saveDir;
|
||||
}
|
||||
|
||||
function doTest(aIsPrivateMode, aWindow, aCallback) {
|
||||
function contextMenuOpened(event) {
|
||||
cache.clear();
|
||||
|
||||
aWindow.document.removeEventListener("popupshown", contextMenuOpened);
|
||||
|
||||
// Create the folder the image will be saved into.
|
||||
var destDir = createTemporarySaveDirectory();
|
||||
var destFile = destDir.clone();
|
||||
|
||||
MockFilePicker.displayDirectory = destDir;
|
||||
MockFilePicker.showCallback = function(fp) {
|
||||
fileName = fp.defaultString;
|
||||
destFile.append (fileName);
|
||||
MockFilePicker.returnFiles = [destFile];
|
||||
MockFilePicker.filterIndex = 1; // kSaveAsType_URL
|
||||
};
|
||||
|
||||
mockTransferCallback = onTransferComplete;
|
||||
mockTransferRegisterer.register();
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
mockTransferRegisterer.unregister();
|
||||
MockFilePicker.cleanup();
|
||||
destDir.remove(true);
|
||||
});
|
||||
|
||||
// Select "Save Image As" option from context menu
|
||||
var saveVideoCommand = aWindow.document.getElementById("context-saveimage");
|
||||
saveVideoCommand.doCommand();
|
||||
|
||||
event.target.hidePopup();
|
||||
}
|
||||
|
||||
aWindow.gBrowser.addEventListener("pageshow", function pageShown(event) {
|
||||
// If data: --url PAC file isn't loaded soon enough, we may get about:privatebrowsing loaded
|
||||
if (event.target.location == "about:blank" ||
|
||||
event.target.location == "about:privatebrowsing") {
|
||||
aWindow.gBrowser.selectedBrowser.loadURI(testURI);
|
||||
return;
|
||||
}
|
||||
aWindow.gBrowser.removeEventListener("pageshow", pageShown);
|
||||
|
||||
waitForFocus(function () {
|
||||
aWindow.document.addEventListener("popupshown", contextMenuOpened, false);
|
||||
var img = aWindow.gBrowser.selectedBrowser.contentDocument.getElementById("img");
|
||||
EventUtils.synthesizeMouseAtCenter(img,
|
||||
{ type: "contextmenu", button: 2 },
|
||||
aWindow.gBrowser.contentWindow);
|
||||
}, aWindow.gBrowser.selectedBrowser);
|
||||
});
|
||||
}
|
||||
|
||||
function testOnWindow(aOptions, aCallback) {
|
||||
whenNewWindowLoaded(aOptions, function(aWin) {
|
||||
windowsToClose.push(aWin);
|
||||
// execute should only be called when need, like when you are opening
|
||||
// web pages on the test. If calling executeSoon() is not necesary, then
|
||||
// call whenNewWindowLoaded() instead of testOnWindow() on your test.
|
||||
executeSoon(() => aCallback(aWin));
|
||||
});
|
||||
};
|
||||
|
||||
// this function is called after calling finish() on the test.
|
||||
registerCleanupFunction(function() {
|
||||
windowsToClose.forEach(function(aWin) {
|
||||
aWin.close();
|
||||
});
|
||||
});
|
||||
|
||||
MockFilePicker.init(window);
|
||||
// then test when on private mode
|
||||
testOnWindow({private: true}, function(aWin) {
|
||||
doTest(true, aWin, finish);
|
||||
});
|
||||
}
|
||||
|
||||
function promiseImageDownloaded() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let fileName;
|
||||
let MockFilePicker = SpecialPowers.MockFilePicker;
|
||||
MockFilePicker.init(window);
|
||||
|
||||
function onTransferComplete(downloadSuccess) {
|
||||
ok(downloadSuccess, "Image file should have been downloaded successfully " + fileName);
|
||||
|
||||
// Give the request a chance to finish and create a cache entry
|
||||
resolve(fileName);
|
||||
}
|
||||
|
||||
// Create the folder the image will be saved into.
|
||||
var destDir = createTemporarySaveDirectory();
|
||||
var destFile = destDir.clone();
|
||||
|
||||
MockFilePicker.displayDirectory = destDir;
|
||||
MockFilePicker.showCallback = function(fp) {
|
||||
fileName = fp.defaultString;
|
||||
destFile.append (fileName);
|
||||
MockFilePicker.returnFiles = [destFile];
|
||||
MockFilePicker.filterIndex = 1; // kSaveAsType_URL
|
||||
};
|
||||
|
||||
mockTransferCallback = onTransferComplete;
|
||||
mockTransferRegisterer.register();
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
mockTransferCallback = null;
|
||||
mockTransferRegisterer.unregister();
|
||||
MockFilePicker.cleanup();
|
||||
destDir.remove(true);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* () {
|
||||
let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.html";
|
||||
let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(privateWindow.gBrowser, testURI);
|
||||
|
||||
let contextMenu = privateWindow.document.getElementById("contentAreaContextMenu");
|
||||
let popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
|
||||
let popupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("#img", {
|
||||
type: "contextmenu",
|
||||
button: 2
|
||||
}, tab.linkedBrowser);
|
||||
yield popupShown;
|
||||
|
||||
let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
|
||||
.getService(Ci.nsICacheStorageService);
|
||||
cache.clear();
|
||||
|
||||
let imageDownloaded = promiseImageDownloaded();
|
||||
// Select "Save Image As" option from context menu
|
||||
privateWindow.document.getElementById("context-saveimage").doCommand();
|
||||
|
||||
contextMenu.hidePopup();
|
||||
yield popupHidden;
|
||||
|
||||
// wait for image download
|
||||
let fileName = yield imageDownloaded;
|
||||
yield promiseNoCacheEntry(fileName);
|
||||
|
||||
yield BrowserTestUtils.closeWindow(privateWindow);
|
||||
});
|
||||
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
|
||||
|
@ -0,0 +1,3 @@
|
||||
<!ENTITY foo.bar "This string
|
||||
contains
|
||||
newlines!">
|
@ -611,9 +611,19 @@ function promiseTabLoadEvent(tab, url)
|
||||
let deferred = Promise.defer();
|
||||
info("Wait tab event: load");
|
||||
|
||||
function handle(loadedUrl) {
|
||||
if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
|
||||
info(`Skipping spurious load event for ${loadedUrl}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
info("Tab event received: load");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create two promises: one resolved from the content process when the page
|
||||
// loads and one that is rejected if we take too long to load the url.
|
||||
let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url);
|
||||
let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
|
||||
|
||||
let timeout = setTimeout(() => {
|
||||
deferred.reject(new Error("Timed out while waiting for a 'load' event"));
|
||||
|
@ -10,7 +10,6 @@
|
||||
orient="vertical">
|
||||
|
||||
<broadcasterset>
|
||||
<broadcaster id="identity-popup-content-host" class="identity-popup-headline" crop="start"/>
|
||||
<broadcaster id="identity-popup-mcb-learn-more" class="text-link plain" value="&identity.learnMore;"/>
|
||||
<broadcaster id="identity-popup-insecure-login-forms-learn-more" class="text-link plain" value="&identity.learnMore;"/>
|
||||
</broadcasterset>
|
||||
@ -22,7 +21,10 @@
|
||||
<!-- Security Section -->
|
||||
<hbox id="identity-popup-security" class="identity-popup-section">
|
||||
<vbox id="identity-popup-security-content" flex="1">
|
||||
<label observes="identity-popup-content-host"/>
|
||||
<label class="plain">
|
||||
<label class="identity-popup-headline host"></label>
|
||||
<label class="identity-popup-headline hostless" crop="end"/>
|
||||
</label>
|
||||
<description class="identity-popup-connection-not-secure"
|
||||
value="&identity.connectionNotSecure;"
|
||||
when-connection="not-secure secure-cert-user-overridden"/>
|
||||
@ -96,7 +98,10 @@
|
||||
<!-- Security SubView -->
|
||||
<panelview id="identity-popup-securityView" flex="1">
|
||||
<vbox id="identity-popup-securityView-header">
|
||||
<label observes="identity-popup-content-host"/>
|
||||
<label class="plain">
|
||||
<label class="identity-popup-headline host"></label>
|
||||
<label class="identity-popup-headline hostless" crop="end"/>
|
||||
</label>
|
||||
<description class="identity-popup-connection-not-secure"
|
||||
value="&identity.connectionNotSecure;"
|
||||
when-connection="not-secure secure-cert-user-overridden"/>
|
||||
|
@ -462,7 +462,7 @@ this.DownloadsCommon = {
|
||||
if (!shouldLaunch) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Actually open the file.
|
||||
try {
|
||||
if (aMimeInfo && aMimeInfo.preferredAction == aMimeInfo.useHelperApp) {
|
||||
@ -470,7 +470,7 @@ this.DownloadsCommon = {
|
||||
return;
|
||||
}
|
||||
} catch (ex) { }
|
||||
|
||||
|
||||
// If either we don't have the mime info, or the preferred action failed,
|
||||
// attempt to launch the file directly.
|
||||
try {
|
||||
@ -521,49 +521,83 @@ this.DownloadsCommon = {
|
||||
* Displays an alert message box which asks the user if they want to
|
||||
* unblock the downloaded file or not.
|
||||
*
|
||||
* @param aVerdict
|
||||
* The detailed reason why the download was blocked, according to the
|
||||
* "Downloads.Error.BLOCK_VERDICT_" constants. If an unknown reason is
|
||||
* specified, "Downloads.Error.BLOCK_VERDICT_MALWARE" is assumed.
|
||||
* @param aOwnerWindow
|
||||
* The window with which this action is associated.
|
||||
* @param options
|
||||
* An object with the following properties:
|
||||
* {
|
||||
* verdict:
|
||||
* The detailed reason why the download was blocked, according to
|
||||
* the "Downloads.Error.BLOCK_VERDICT_" constants. If an unknown
|
||||
* reason is specified, "Downloads.Error.BLOCK_VERDICT_MALWARE" is
|
||||
* assumed.
|
||||
* window:
|
||||
* The window with which this action is associated.
|
||||
* dialogType:
|
||||
* String that determines which actions are available:
|
||||
* - "unblock" to offer just "unblock".
|
||||
* - "chooseUnblock" to offer "unblock" and "confirmBlock".
|
||||
* - "chooseOpen" to offer "open" and "confirmBlock".
|
||||
* }
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves String representing the action that should be executed:
|
||||
* - "open" to allow the download and open the file.
|
||||
* - "unblock" to allow the download without opening the file.
|
||||
* - "confirmBlock" to delete the blocked data permanently.
|
||||
* - "cancel" to do nothing and cancel the operation.
|
||||
*/
|
||||
confirmUnblockDownload: Task.async(function* (aVerdict, aOwnerWindow) {
|
||||
confirmUnblockDownload: Task.async(function* ({ verdict, window,
|
||||
dialogType }) {
|
||||
let s = DownloadsCommon.strings;
|
||||
let title = s.unblockHeader;
|
||||
let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
|
||||
(Ci.nsIPrompt.BUTTON_TITLE_CANCEL * Ci.nsIPrompt.BUTTON_POS_1);
|
||||
let type = "";
|
||||
let message = s.unblockTip;
|
||||
let unblockButton = s.unblockButtonContinue;
|
||||
let confirmBlockButton = s.unblockButtonCancel;
|
||||
|
||||
switch (aVerdict) {
|
||||
case Downloads.Error.BLOCK_VERDICT_UNCOMMON:
|
||||
type = s.unblockTypeUncommon;
|
||||
buttonFlags += (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2) +
|
||||
Ci.nsIPrompt.BUTTON_POS_0_DEFAULT;
|
||||
break;
|
||||
case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
|
||||
type = s.unblockTypePotentiallyUnwanted;
|
||||
buttonFlags += (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2) +
|
||||
Ci.nsIPrompt.BUTTON_POS_2_DEFAULT;
|
||||
break;
|
||||
default: // Assume Downloads.Error.BLOCK_VERDICT_MALWARE
|
||||
type = s.unblockTypeMalware;
|
||||
// All the dialogs have an action button and a cancel button, while only
|
||||
// some of them have an additonal button to remove the file. The cancel
|
||||
// button must always be the one at BUTTON_POS_1 because this is the value
|
||||
// returned by confirmEx when using ESC or closing the dialog (bug 345067).
|
||||
let title = s.unblockHeaderUnblock;
|
||||
let firstButtonText = s.unblockButtonUnblock;
|
||||
let firstButtonAction = "unblock";
|
||||
let buttonFlags =
|
||||
(Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
|
||||
(Ci.nsIPrompt.BUTTON_TITLE_CANCEL * Ci.nsIPrompt.BUTTON_POS_1);
|
||||
|
||||
switch (dialogType) {
|
||||
case "unblock":
|
||||
// Use only the unblock action. The default is to cancel.
|
||||
buttonFlags += Ci.nsIPrompt.BUTTON_POS_1_DEFAULT;
|
||||
break;
|
||||
case "chooseUnblock":
|
||||
// Use the unblock and remove file actions. The default is remove file.
|
||||
buttonFlags +=
|
||||
(Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2) +
|
||||
Ci.nsIPrompt.BUTTON_POS_2_DEFAULT;
|
||||
break;
|
||||
case "chooseOpen":
|
||||
// Use the unblock and open file actions. The default is open file.
|
||||
title = s.unblockHeaderOpen;
|
||||
firstButtonText = s.unblockButtonOpen;
|
||||
firstButtonAction = "open";
|
||||
buttonFlags +=
|
||||
(Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2) +
|
||||
Ci.nsIPrompt.BUTTON_POS_0_DEFAULT;
|
||||
break;
|
||||
default:
|
||||
Cu.reportError("Unexpected dialog type: " + dialogType);
|
||||
return "cancel";
|
||||
}
|
||||
|
||||
if (type) {
|
||||
message = type + "\n\n" + message;
|
||||
let message;
|
||||
switch (verdict) {
|
||||
case Downloads.Error.BLOCK_VERDICT_UNCOMMON:
|
||||
message = s.unblockTypeUncommon;
|
||||
break;
|
||||
case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
|
||||
message = s.unblockTypePotentiallyUnwanted;
|
||||
break;
|
||||
default: // Assume Downloads.Error.BLOCK_VERDICT_MALWARE
|
||||
message = s.unblockTypeMalware;
|
||||
break;
|
||||
}
|
||||
message += "\n\n" + s.unblockTip;
|
||||
|
||||
Services.ww.registerNotification(function onOpen(subj, topic) {
|
||||
if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
|
||||
@ -584,12 +618,10 @@ this.DownloadsCommon = {
|
||||
}
|
||||
});
|
||||
|
||||
// The ordering of the ok/cancel buttons is used this way to allow "cancel"
|
||||
// to have the same result as hitting the ESC or Close button (see bug 345067).
|
||||
let rv = Services.prompt.confirmEx(aOwnerWindow, title, message, buttonFlags,
|
||||
unblockButton, null, confirmBlockButton,
|
||||
null, {});
|
||||
return ["unblock", "cancel", "confirmBlock"][rv];
|
||||
let rv = Services.prompt.confirmEx(window, title, message, buttonFlags,
|
||||
firstButtonText, null,
|
||||
s.unblockButtonConfirmBlock, null, {});
|
||||
return [firstButtonAction, "cancel", "confirmBlock"][rv];
|
||||
}),
|
||||
};
|
||||
|
||||
|
@ -237,7 +237,7 @@ this.DownloadsViewUI.DownloadElementShell.prototype = {
|
||||
} else if (this.download.error.becauseBlockedByReputationCheck) {
|
||||
switch (this.download.error.reputationCheckVerdict) {
|
||||
case Downloads.Error.BLOCK_VERDICT_UNCOMMON:
|
||||
stateLabel = s.blockedUncommon;
|
||||
stateLabel = s.blockedUncommon2;
|
||||
break;
|
||||
case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
|
||||
stateLabel = s.blockedPotentiallyUnwanted;
|
||||
@ -271,11 +271,18 @@ this.DownloadsViewUI.DownloadElementShell.prototype = {
|
||||
*
|
||||
* @param window
|
||||
* The window to which the dialog should be anchored.
|
||||
* @param dialogType
|
||||
* Can be "unblock", "chooseUnblock", or "chooseOpen".
|
||||
*/
|
||||
confirmUnblock(window) {
|
||||
let verdict = this.download.error.reputationCheckVerdict;
|
||||
DownloadsCommon.confirmUnblockDownload(verdict, window).then(action => {
|
||||
if (action == "unblock") {
|
||||
confirmUnblock(window, dialogType) {
|
||||
DownloadsCommon.confirmUnblockDownload({
|
||||
verdict: this.download.error.reputationCheckVerdict,
|
||||
window,
|
||||
dialogType,
|
||||
}).then(action => {
|
||||
if (action == "open") {
|
||||
return this.download.unblock().then(() => this.downloadsCmd_open());
|
||||
} else if (action == "unblock") {
|
||||
return this.download.unblock();
|
||||
} else if (action == "confirmBlock") {
|
||||
return this.download.confirmBlock();
|
||||
@ -323,6 +330,8 @@ this.DownloadsViewUI.DownloadElementShell.prototype = {
|
||||
case "downloadsCmd_openReferrer":
|
||||
return !!this.download.source.referrer;
|
||||
case "downloadsCmd_confirmBlock":
|
||||
case "downloadsCmd_chooseUnblock":
|
||||
case "downloadsCmd_chooseOpen":
|
||||
case "downloadsCmd_unblock":
|
||||
return this.download.hasBlockedData;
|
||||
}
|
||||
|
@ -377,7 +377,15 @@ HistoryDownloadElementShell.prototype = {
|
||||
},
|
||||
|
||||
downloadsCmd_unblock() {
|
||||
this.confirmUnblock(window);
|
||||
this.confirmUnblock(window, "unblock");
|
||||
},
|
||||
|
||||
downloadsCmd_chooseUnblock() {
|
||||
this.confirmUnblock(window, "chooseUnblock");
|
||||
},
|
||||
|
||||
downloadsCmd_chooseOpen() {
|
||||
this.confirmUnblock(window, "chooseOpen");
|
||||
},
|
||||
|
||||
// Returns whether or not the download handled by this shell should
|
||||
|
@ -63,6 +63,10 @@
|
||||
oncommand="goDoCommand('downloadsCmd_cancel')"/>
|
||||
<command id="downloadsCmd_unblock"
|
||||
oncommand="goDoCommand('downloadsCmd_unblock')"/>
|
||||
<command id="downloadsCmd_chooseUnblock"
|
||||
oncommand="goDoCommand('downloadsCmd_chooseUnblock')"/>
|
||||
<command id="downloadsCmd_chooseOpen"
|
||||
oncommand="goDoCommand('downloadsCmd_chooseOpen')"/>
|
||||
<command id="downloadsCmd_confirmBlock"
|
||||
oncommand="goDoCommand('downloadsCmd_confirmBlock')"/>
|
||||
<command id="downloadsCmd_open"
|
||||
@ -92,8 +96,8 @@
|
||||
accesskey="&cmd.cancel.accesskey;"/>
|
||||
<menuitem command="downloadsCmd_unblock"
|
||||
class="downloadUnblockMenuItem"
|
||||
label="&cmd.unblock.label;"
|
||||
accesskey="&cmd.unblock.accesskey;"/>
|
||||
label="&cmd.unblock2.label;"
|
||||
accesskey="&cmd.unblock2.accesskey;"/>
|
||||
<menuitem command="cmd_delete"
|
||||
class="downloadRemoveFromHistoryMenuItem"
|
||||
label="&cmd.removeFromHistory.label;"
|
||||
|
@ -63,9 +63,12 @@
|
||||
<xul:button class="downloadButton downloadConfirmBlock downloadIconCancel"
|
||||
tooltiptext="&cmd.removeFile.label;"
|
||||
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_confirmBlock');"/>
|
||||
<xul:button class="downloadButton downloadUnblock downloadIconShow"
|
||||
tooltiptext="&cmd.unblock.label;"
|
||||
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_unblock');"/>
|
||||
<xul:button class="downloadButton downloadChooseUnblock downloadIconShow"
|
||||
tooltiptext="&cmd.chooseUnblock.label;"
|
||||
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_chooseUnblock');"/>
|
||||
<xul:button class="downloadButton downloadChooseOpen downloadIconShow"
|
||||
tooltiptext="&cmd.chooseOpen.label;"
|
||||
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_chooseOpen');"/>
|
||||
</xul:stack>
|
||||
</content>
|
||||
</binding>
|
||||
|
@ -127,13 +127,22 @@ richlistitem.download button {
|
||||
.downloadConfirmBlock,
|
||||
|
||||
/* Blocked (dirty) downloads that have not been confirmed and
|
||||
have temporary data, for cases other than Malware. */
|
||||
have temporary data, for the Potentially Unwanted case. */
|
||||
.download-state:not( [state="8"] /* Blocked (dirty) */)
|
||||
.downloadUnblock,
|
||||
.downloadChooseUnblock,
|
||||
.download-state[state="8"]:not(.temporary-block)
|
||||
.downloadUnblock,
|
||||
.download-state[state="8"].temporary-block[verdict="Malware"]
|
||||
.downloadUnblock,
|
||||
.downloadChooseUnblock,
|
||||
.download-state[state="8"].temporary-block:not([verdict="PotentiallyUnwanted"])
|
||||
.downloadChooseUnblock,
|
||||
|
||||
/* Blocked (dirty) downloads that have not been confirmed and
|
||||
have temporary data, for the Uncommon case. */
|
||||
.download-state:not( [state="8"] /* Blocked (dirty) */)
|
||||
.downloadChooseOpen,
|
||||
.download-state[state="8"]:not(.temporary-block)
|
||||
.downloadChooseOpen,
|
||||
.download-state[state="8"].temporary-block:not([verdict="Uncommon"])
|
||||
.downloadChooseOpen,
|
||||
|
||||
.download-state:not(:-moz-any([state="2"], /* Failed */
|
||||
[state="3"]) /* Canceled */)
|
||||
|
@ -1092,7 +1092,17 @@ DownloadsViewItem.prototype = {
|
||||
|
||||
downloadsCmd_unblock() {
|
||||
DownloadsPanel.hidePanel();
|
||||
this.confirmUnblock(window);
|
||||
this.confirmUnblock(window, "unblock");
|
||||
},
|
||||
|
||||
downloadsCmd_chooseUnblock() {
|
||||
DownloadsPanel.hidePanel();
|
||||
this.confirmUnblock(window, "chooseUnblock");
|
||||
},
|
||||
|
||||
downloadsCmd_chooseOpen() {
|
||||
DownloadsPanel.hidePanel();
|
||||
this.confirmUnblock(window, "chooseOpen");
|
||||
},
|
||||
|
||||
downloadsCmd_open() {
|
||||
|
@ -23,6 +23,10 @@
|
||||
oncommand="goDoCommand('downloadsCmd_cancel')"/>
|
||||
<command id="downloadsCmd_unblock"
|
||||
oncommand="goDoCommand('downloadsCmd_unblock')"/>
|
||||
<command id="downloadsCmd_chooseUnblock"
|
||||
oncommand="goDoCommand('downloadsCmd_chooseUnblock')"/>
|
||||
<command id="downloadsCmd_chooseOpen"
|
||||
oncommand="goDoCommand('downloadsCmd_chooseOpen')"/>
|
||||
<command id="downloadsCmd_confirmBlock"
|
||||
oncommand="goDoCommand('downloadsCmd_confirmBlock')"/>
|
||||
<command id="downloadsCmd_open"
|
||||
@ -71,8 +75,8 @@
|
||||
accesskey="&cmd.cancel.accesskey;"/>
|
||||
<menuitem command="downloadsCmd_unblock"
|
||||
class="downloadUnblockMenuItem"
|
||||
label="&cmd.unblock.label;"
|
||||
accesskey="&cmd.unblock.accesskey;"/>
|
||||
label="&cmd.unblock2.label;"
|
||||
accesskey="&cmd.unblock2.accesskey;"/>
|
||||
<menuitem command="cmd_delete"
|
||||
class="downloadRemoveFromHistoryMenuItem"
|
||||
label="&cmd.removeFromHistory.label;"
|
||||
|
@ -31,16 +31,86 @@ function addDialogOpenObserver(buttonAction) {
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* test_confirm_unblock_dialog_unblock() {
|
||||
addDialogOpenObserver("accept");
|
||||
let result = yield DownloadsCommon.confirmUnblockDownload(Downloads.Error.BLOCK_VERDICT_MALWARE,
|
||||
window);
|
||||
is(result, "unblock");
|
||||
function* assertDialogResult({ args, buttonToClick, expectedResult }) {
|
||||
addDialogOpenObserver(buttonToClick);
|
||||
is(yield DownloadsCommon.confirmUnblockDownload(args), expectedResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the "unblock" dialog, for each of the possible verdicts.
|
||||
*/
|
||||
add_task(function* test_unblock_dialog_unblock() {
|
||||
for (let verdict of [Downloads.Error.BLOCK_VERDICT_MALWARE,
|
||||
Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED,
|
||||
Downloads.Error.BLOCK_VERDICT_UNCOMMON]) {
|
||||
let args = { verdict, window, dialogType: "unblock" };
|
||||
|
||||
// Test both buttons.
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "accept",
|
||||
expectedResult: "unblock",
|
||||
});
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "cancel",
|
||||
expectedResult: "cancel",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_confirm_unblock_dialog_keep_safe() {
|
||||
addDialogOpenObserver("cancel");
|
||||
let result = yield DownloadsCommon.confirmUnblockDownload(Downloads.Error.BLOCK_VERDICT_MALWARE,
|
||||
window);
|
||||
is(result, "cancel");
|
||||
/**
|
||||
* Tests the "chooseUnblock" dialog for potentially unwanted downloads.
|
||||
*/
|
||||
add_task(function* test_chooseUnblock_dialog() {
|
||||
let args = {
|
||||
verdict: Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED,
|
||||
window,
|
||||
dialogType: "chooseUnblock",
|
||||
};
|
||||
|
||||
// Test each of the three buttons.
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "accept",
|
||||
expectedResult: "unblock",
|
||||
});
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "cancel",
|
||||
expectedResult: "cancel",
|
||||
});
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "extra1",
|
||||
expectedResult: "confirmBlock",
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests the "chooseOpen" dialog for uncommon downloads.
|
||||
*/
|
||||
add_task(function* test_chooseOpen_dialog() {
|
||||
let args = {
|
||||
verdict: Downloads.Error.BLOCK_VERDICT_UNCOMMON,
|
||||
window,
|
||||
dialogType: "chooseOpen",
|
||||
};
|
||||
|
||||
// Test each of the three buttons.
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "accept",
|
||||
expectedResult: "open",
|
||||
});
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "cancel",
|
||||
expectedResult: "cancel",
|
||||
});
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "extra1",
|
||||
expectedResult: "confirmBlock",
|
||||
});
|
||||
});
|
||||
|
@ -66,18 +66,27 @@
|
||||
<!ENTITY cmd.clearList.accesskey "a">
|
||||
<!ENTITY cmd.clearDownloads.label "Clear Downloads">
|
||||
<!ENTITY cmd.clearDownloads.accesskey "D">
|
||||
<!-- LOCALIZATION NOTE (cmd.unblock.label):
|
||||
This command may be shown in the context menu, as a menu button item, or as
|
||||
a text link when malware or potentially unwanted downloads are blocked.
|
||||
<!-- LOCALIZATION NOTE (cmd.unblock2.label):
|
||||
This command is shown in the context menu when downloads are blocked.
|
||||
-->
|
||||
<!ENTITY cmd.unblock.label "Unblock">
|
||||
<!ENTITY cmd.unblock.accesskey "U">
|
||||
<!ENTITY cmd.unblock2.label "Allow Download">
|
||||
<!ENTITY cmd.unblock2.accesskey "o">
|
||||
<!-- LOCALIZATION NOTE (cmd.removeFile.label):
|
||||
This command may be shown in the context menu or as a menu button label
|
||||
when malware or potentially unwanted downloads are blocked.
|
||||
This is the tooltip of the action button shown when malware is blocked.
|
||||
-->
|
||||
<!ENTITY cmd.removeFile.label "Remove File">
|
||||
<!ENTITY cmd.removeFile.accesskey "m">
|
||||
<!-- LOCALIZATION NOTE (cmd.chooseUnblock.tooltip):
|
||||
This is the tooltip of the action button shown when potentially unwanted
|
||||
downloads are blocked. This opens a dialog where the user can choose
|
||||
whether to unblock or remove the download. Removing is the default option.
|
||||
-->
|
||||
<!ENTITY cmd.chooseUnblock.label "Remove File or Allow Download">
|
||||
<!-- LOCALIZATION NOTE (cmd.chooseOpen.tooltip):
|
||||
This is the tooltip of the action button shown when uncommon downloads are
|
||||
blocked.This opens a dialog where the user can choose whether to open the
|
||||
file or remove the download. Opening is the default option.
|
||||
-->
|
||||
<!ENTITY cmd.chooseOpen.label "Open or Remove File">
|
||||
|
||||
<!-- LOCALIZATION NOTE (blocked.label):
|
||||
Shown as a tag before the file name for some types of blocked downloads.
|
||||
|
@ -39,7 +39,7 @@ stateBlockedPolicy=Blocked by your security zone policy
|
||||
stateDirty=Blocked: May contain a virus or spyware
|
||||
|
||||
# LOCALIZATION NOTE (blockedMalware, blockedPotentiallyUnwanted,
|
||||
# blockedUncommon):
|
||||
# blockedUncommon2):
|
||||
# These strings are shown in the panel for some types of blocked downloads, and
|
||||
# are immediately followed by the "Learn More" link, thus they must end with a
|
||||
# period. You may need to adjust "downloadDetails.width" in "downloads.dtd" if
|
||||
@ -47,23 +47,25 @@ stateDirty=Blocked: May contain a virus or spyware
|
||||
# Note: These strings don't exist in the UI yet. See bug 1053890.
|
||||
blockedMalware=This file contains a virus or malware.
|
||||
blockedPotentiallyUnwanted=This file may harm your computer.
|
||||
blockedUncommon=This file may not be safe to open.
|
||||
blockedUncommon2=This file is not commonly downloaded.
|
||||
|
||||
# LOCALIZATION NOTE (unblockHeader, unblockTypeMalware,
|
||||
# unblockTypePotentiallyUnwanted, unblockTypeUncommon,
|
||||
# unblockTip, unblockButtonContinue, unblockButtonCancel):
|
||||
# LOCALIZATION NOTE (unblockHeaderUnblock, unblockHeaderOpen,
|
||||
# unblockTypeMalware, unblockTypePotentiallyUnwanted,
|
||||
# unblockTypeUncommon, unblockTip, unblockButtonOpen,
|
||||
# unblockButtonUnblock, unblockButtonConfirmBlock):
|
||||
# These strings are displayed in the dialog shown when the user asks a blocked
|
||||
# download to be unblocked. The severity of the threat is expressed in
|
||||
# descending order by the unblockType strings, it is higher for files detected
|
||||
# as malware and lower for uncommon downloads.
|
||||
# Note: These strings don't exist in the UI yet. See bug 1053890.
|
||||
unblockHeader=Are you sure you want to unblock this file?
|
||||
unblockHeaderUnblock=Are you sure you want to allow this download?
|
||||
unblockHeaderOpen=Are you sure you want to open this file?
|
||||
unblockTypeMalware=This file contains a virus or other malware that will harm your computer.
|
||||
unblockTypePotentiallyUnwanted=This file, disguised as a helpful download, will make unexpected changes to your programs and settings.
|
||||
unblockTypeUncommon=This file has been downloaded from an unfamiliar and potentially dangerous website and may not be safe to open.
|
||||
unblockTip=You can search for an alternate download source or try to download the file again later.
|
||||
unblockButtonContinue=Unblock anyway
|
||||
unblockButtonCancel=Keep me safe
|
||||
unblockButtonOpen=Open
|
||||
unblockButtonUnblock=Allow download
|
||||
unblockButtonConfirmBlock=Remove file
|
||||
|
||||
# LOCALIZATION NOTE (sizeWithUnits):
|
||||
# %1$S is replaced with the size number, and %2$S with the measurement unit.
|
||||
|
@ -4,8 +4,15 @@
|
||||
|
||||
<!ENTITY safeb.palm.accept.label "Get me out of here!">
|
||||
<!ENTITY safeb.palm.decline.label "Ignore this warning">
|
||||
<!-- Localization note (safeb.palm.notdeceptive.label) - Label of the Help menu item. -->
|
||||
<!-- Localization note (safeb.palm.notdeceptive.label) - Label of the Help menu
|
||||
item. Either this or reportDeceptiveSiteMenu.label from report-phishing.dtd is
|
||||
shown. -->
|
||||
<!ENTITY safeb.palm.notdeceptive.label "This isn’t a deceptive site…">
|
||||
<!-- Localization note (safeb.palm.notdeceptive.accesskey) - Because
|
||||
safeb.palm.notdeceptive.label and reportDeceptiveSiteMenu.title from
|
||||
report-phishing.dtd are never shown at the same time, the same accesskey can
|
||||
be used for them. -->
|
||||
<!ENTITY safeb.palm.notdeceptive.accesskey "d">
|
||||
<!ENTITY safeb.palm.reportPage.label "Why was this page blocked?">
|
||||
<!ENTITY safeb.palm.whyForbidden.label "Why was this page blocked?">
|
||||
|
||||
|
@ -2,5 +2,12 @@
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<!-- Localization note (reportDeceptiveSiteMenu.title) - Label of the Help menu
|
||||
item. Either this or safeb.palm.notdeceptive.label from
|
||||
phishing-afterload-warning-message.dtd is shown. -->
|
||||
<!ENTITY reportDeceptiveSiteMenu.title "Report deceptive site…">
|
||||
<!-- Localization note (reportDeceptiveSiteMenu.accesskey) - Because
|
||||
safeb.palm.notdeceptive.label from phishing-afterload-warning-message.dtd and
|
||||
reportDeceptiveSiteMenu.title are never shown at the same time, the same
|
||||
accesskey can be used for them. -->
|
||||
<!ENTITY reportDeceptiveSiteMenu.accesskey "D">
|
||||
|
@ -103,6 +103,7 @@
|
||||
#identity-popup-permissions-content,
|
||||
#tracking-protection-content {
|
||||
padding: 0.5em 0 1em;
|
||||
/* .identity-popup-headline.host depends on this width */
|
||||
-moz-padding-start: calc(2em + 24px);
|
||||
-moz-padding-end: 1em;
|
||||
}
|
||||
@ -120,7 +121,7 @@
|
||||
margin: 0;
|
||||
padding: 4px 0;
|
||||
min-width: auto;
|
||||
width: 38px;
|
||||
width: var(--identity-popup-expander-width);
|
||||
border: 0 none;
|
||||
-moz-appearance: none;
|
||||
background-image: url("chrome://browser/skin/controlcenter/arrow-subview.svg"),
|
||||
@ -181,6 +182,13 @@
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.identity-popup-headline.host {
|
||||
word-wrap: break-word;
|
||||
/* 1em + 2em + 24px is #identity-popup-security-content padding
|
||||
* 30em is .panel-mainview:not([panelid="PanelUI-popup"]) width */
|
||||
max-width: calc(30rem - 3rem - 24px - var(--identity-popup-expander-width))
|
||||
}
|
||||
|
||||
.identity-popup-warning-gray {
|
||||
-moz-padding-start: 24px;
|
||||
background: url(chrome://browser/skin/controlcenter/warning-gray.svg) no-repeat 0 50%;
|
||||
|
@ -34,6 +34,8 @@ support-files =
|
||||
support-files =
|
||||
browser_cmd_cookie.html
|
||||
[browser_cmd_cookie_host.js]
|
||||
support-files =
|
||||
browser_cmd_cookie.html
|
||||
[browser_cmd_csscoverage_oneshot.js]
|
||||
support-files =
|
||||
browser_cmd_csscoverage_page1.html
|
||||
|
@ -11,6 +11,7 @@
|
||||
<script type="text/javascript">
|
||||
document.cookie = "zap=zep";
|
||||
document.cookie = "zip=zop";
|
||||
document.cookie = "zig=zag; domain=.mochi.test";
|
||||
document.getElementById("result").innerHTML = document.cookie;
|
||||
</script>
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that the cookie command works for host with a port specified
|
||||
|
||||
const TEST_URI = "http://mochi.test:8888/browser/devtools/client/commandline/"+
|
||||
const TEST_URI = "http://mochi.test:8888/browser/devtools/client/commandline/" +
|
||||
"test/browser_cmd_cookie.html";
|
||||
|
||||
function test() {
|
||||
@ -12,7 +14,7 @@ function test() {
|
||||
{
|
||||
setup: 'cookie list',
|
||||
exec: {
|
||||
output: [ /zap=zep/, /zip=zop/ ],
|
||||
output: [ /zap=zep/, /zip=zop/, /zig=zag/ ],
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -30,7 +32,7 @@ function test() {
|
||||
{
|
||||
setup: "cookie list",
|
||||
exec: {
|
||||
output: [ /zap=zep/, /zip=zop/, /zup=banana/, /Edit/ ]
|
||||
output: [ /zap=zep/, /zip=zop/, /zig=zag/, /zup=banana/, /Edit/ ]
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "test content script sources",
|
||||
"description": "test content script sources",
|
||||
"version": "0.1.0",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "test-contentscript-sources@mozilla.com"
|
||||
}
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["webext-content-script.js"],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1 @@
|
||||
console.log("CONTENT SCRIPT LOADED");
|
Binary file not shown.
@ -8,6 +8,7 @@ support-files =
|
||||
addon3.xpi
|
||||
addon4.xpi
|
||||
addon5.xpi
|
||||
addon-webext-contentscript.xpi
|
||||
code_binary_search.coffee
|
||||
code_binary_search.js
|
||||
code_binary_search.map
|
||||
@ -109,6 +110,7 @@ support-files =
|
||||
doc_script-bookmarklet.html
|
||||
doc_script-switching-01.html
|
||||
doc_script-switching-02.html
|
||||
doc_script_webext_contentscript.html
|
||||
doc_split-console-paused-reload.html
|
||||
doc_step-many-statements.html
|
||||
doc_step-out.html
|
||||
@ -466,6 +468,7 @@ skip-if = e10s && debug
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_sources-bookmarklet.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_sources-webext-contentscript.js]
|
||||
[browser_dbg_split-console-paused-reload.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_stack-01.js]
|
||||
|
@ -0,0 +1,61 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Make sure eval scripts appear in the source list
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_script_webext_contentscript.html";
|
||||
|
||||
function test() {
|
||||
let gPanel, gDebugger;
|
||||
let gSources, gAddon;
|
||||
|
||||
let cleanup = function* (e) {
|
||||
if (gAddon) {
|
||||
// Remove the addon, if any.
|
||||
yield removeAddon(gAddon);
|
||||
}
|
||||
if (gPanel) {
|
||||
// Close the debugger panel, if any.
|
||||
yield closeDebuggerAndFinish(gPanel);
|
||||
} else {
|
||||
// If no debugger panel was opened, call finish directly.
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
return Task.spawn(function* () {
|
||||
gAddon = yield addAddon(EXAMPLE_URL + "/addon-webext-contentscript.xpi");
|
||||
|
||||
[,, gPanel] = yield initDebugger(TAB_URL);
|
||||
gDebugger = gPanel.panelWin;
|
||||
gSources = gDebugger.DebuggerView.Sources;
|
||||
|
||||
// Wait for a SOURCE_SHOWN event for at most 4 seconds.
|
||||
yield Promise.race([
|
||||
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN),
|
||||
waitForTime(4000),
|
||||
]);
|
||||
|
||||
is(gSources.values.length, 1, "Should have 1 source");
|
||||
|
||||
let item = gSources.getItemForAttachment(attachment => {
|
||||
return attachment.source.url.includes("moz-extension");
|
||||
});
|
||||
|
||||
ok(item, "Got the expected WebExtensions ContentScript source");
|
||||
ok(item && item.attachment.source.url.includes(item.attachment.group),
|
||||
"The source is in the expected source group");
|
||||
is(item && item.attachment.label, "webext-content-script.js",
|
||||
"Got the expected filename in the label");
|
||||
|
||||
yield cleanup();
|
||||
}).catch((e) => {
|
||||
ok(false, `Got an unexpected exception: ${e}`);
|
||||
// Cleanup in case of failures in the above task.
|
||||
return Task.spawn(cleanup);
|
||||
});
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Debugger test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -165,18 +165,23 @@ function synthesizeKeyFromKeyTag(key) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for eventName on target.
|
||||
* @param {Object} target An observable object that either supports on/off or
|
||||
* addEventListener/removeEventListener
|
||||
* @param {String} eventName
|
||||
* @param {Boolean} useCapture Optional, for
|
||||
* Wait for eventName on target to be delivered a number of times.
|
||||
*
|
||||
* @param {Object} target
|
||||
* An observable object that either supports on/off or
|
||||
* addEventListener/removeEventListener
|
||||
* @param {String} eventName
|
||||
* @param {Number} numTimes
|
||||
* Number of deliveries to wait for.
|
||||
* @param {Boolean} useCapture
|
||||
* Optional, for addEventListener/removeEventListener
|
||||
* @return A promise that resolves when the event has been handled
|
||||
*/
|
||||
function once(target, eventName, useCapture = false) {
|
||||
function waitForNEvents(target, eventName, numTimes, useCapture = false) {
|
||||
info("Waiting for event: '" + eventName + "' on " + target + ".");
|
||||
|
||||
let deferred = promise.defer();
|
||||
let count = 0;
|
||||
|
||||
for (let [add, remove] of [
|
||||
["addEventListener", "removeEventListener"],
|
||||
@ -186,8 +191,10 @@ function once(target, eventName, useCapture = false) {
|
||||
if ((add in target) && (remove in target)) {
|
||||
target[add](eventName, function onEvent(...aArgs) {
|
||||
info("Got event: '" + eventName + "' on " + target + ".");
|
||||
target[remove](eventName, onEvent, useCapture);
|
||||
deferred.resolve.apply(deferred, aArgs);
|
||||
if (++count == numTimes) {
|
||||
target[remove](eventName, onEvent, useCapture);
|
||||
deferred.resolve.apply(deferred, aArgs);
|
||||
}
|
||||
}, useCapture);
|
||||
break;
|
||||
}
|
||||
@ -196,6 +203,21 @@ function once(target, eventName, useCapture = false) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for eventName on target.
|
||||
*
|
||||
* @param {Object} target
|
||||
* An observable object that either supports on/off or
|
||||
* addEventListener/removeEventListener
|
||||
* @param {String} eventName
|
||||
* @param {Boolean} useCapture
|
||||
* Optional, for addEventListener/removeEventListener
|
||||
* @return A promise that resolves when the event has been handled
|
||||
*/
|
||||
function once(target, eventName, useCapture = false) {
|
||||
return waitForNEvents(target, eventName, 1, useCapture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some tests may need to import one or more of the test helper scripts.
|
||||
* A test helper script is simply a js file that contains common test code that
|
||||
@ -222,6 +244,20 @@ function waitForTick() {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* This shouldn't be used in the tests, but is useful when writing new tests or
|
||||
* debugging existing tests in order to introduce delays in the test steps
|
||||
*
|
||||
* @param {Number} ms
|
||||
* The time to wait
|
||||
* @return A promise that resolves when the time is passed
|
||||
*/
|
||||
function wait(ms) {
|
||||
let def = promise.defer();
|
||||
content.setTimeout(def.resolve, ms);
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the toolbox in a given tab.
|
||||
* @param {XULNode} tab The tab the toolbox should be opened in.
|
||||
|
@ -62,6 +62,8 @@ function setPrefDefaults() {
|
||||
Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true);
|
||||
Services.prefs.setBoolPref("devtools.command-button-noautohide.enabled", true);
|
||||
Services.prefs.setBoolPref("devtools.scratchpad.enabled", true);
|
||||
// Bug 1225160 - Using source maps with browser debugging can lead to a crash
|
||||
Services.prefs.setBoolPref("devtools.debugger.source-maps-enabled", false);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
|
@ -12,50 +12,6 @@ registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.defaultColorUnit");
|
||||
});
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible, and the computed-view
|
||||
* sidebar tab selected.
|
||||
* @return a promise that resolves when the inspector is ready and the computed
|
||||
* view is visible and ready
|
||||
*/
|
||||
function openComputedView() {
|
||||
return openInspectorSidebarTab("computedview").then(({toolbox, inspector}) => {
|
||||
return {
|
||||
toolbox,
|
||||
inspector,
|
||||
view: inspector.computedview.view
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the NodeFront for a given css selector, via the protocol
|
||||
*
|
||||
* @param {String} selector
|
||||
* @param {InspectorPanel} inspector
|
||||
* The instance of InspectorPanel currently loaded in the toolbox
|
||||
* @return {Promise} Resolves to the NodeFront instance
|
||||
*/
|
||||
function getNodeFront(selector, {walker}) {
|
||||
return walker.querySelector(walker.rootNode, selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for a new tab to open and return a promise that resolves when one
|
||||
* does and completes the load event.
|
||||
*
|
||||
* @return a promise that resolves to the tab object
|
||||
*/
|
||||
var waitForTab = Task.async(function*() {
|
||||
info("Waiting for a tab to open");
|
||||
yield once(gBrowser.tabContainer, "TabOpen");
|
||||
let tab = gBrowser.selectedTab;
|
||||
let browser = tab.linkedBrowser;
|
||||
yield once(browser, "load", true);
|
||||
info("The tab load completed");
|
||||
return tab;
|
||||
});
|
||||
|
||||
/**
|
||||
* Dispatch the copy event on the given element
|
||||
*/
|
||||
@ -65,20 +21,6 @@ function fireCopyEvent(element) {
|
||||
element.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate the key input for the given input in the window.
|
||||
*
|
||||
* @param {String} input
|
||||
* The string value to input
|
||||
* @param {Window} win
|
||||
* The window containing the panel
|
||||
*/
|
||||
function synthesizeKeys(input, win) {
|
||||
for (let key of input.split("")) {
|
||||
EventUtils.synthesizeKey(key, {}, win);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get references to the name and value span nodes corresponding to a given
|
||||
* property name in the computed-view
|
||||
|
@ -41,76 +41,6 @@ addTab = function(url) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible, and the rule-view
|
||||
* sidebar tab selected.
|
||||
*
|
||||
* @return a promise that resolves when the inspector is ready and the rule
|
||||
* view is visible and ready
|
||||
*/
|
||||
function openRuleView() {
|
||||
return openInspectorSidebarTab("ruleview").then(data => {
|
||||
return {
|
||||
toolbox: data.toolbox,
|
||||
inspector: data.inspector,
|
||||
testActor: data.testActor,
|
||||
view: data.inspector.ruleview.view
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the inspector's current selection to null so that no node is selected
|
||||
*
|
||||
* @param {InspectorPanel} inspector
|
||||
* The instance of InspectorPanel currently loaded in the toolbox
|
||||
* @return a promise that resolves when the inspector is updated
|
||||
*/
|
||||
function clearCurrentNodeSelection(inspector) {
|
||||
info("Clearing the current selection");
|
||||
let updated = inspector.once("inspector-updated");
|
||||
inspector.selection.setNodeFront(null);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for eventName on target to be delivered a number of times.
|
||||
*
|
||||
* @param {Object} target
|
||||
* An observable object that either supports on/off or
|
||||
* addEventListener/removeEventListener
|
||||
* @param {String} eventName
|
||||
* @param {Number} numTimes
|
||||
* Number of deliveries to wait for.
|
||||
* @param {Boolean} useCapture
|
||||
* Optional, for addEventListener/removeEventListener
|
||||
* @return A promise that resolves when the event has been handled
|
||||
*/
|
||||
function waitForNEvents(target, eventName, numTimes, useCapture = false) {
|
||||
info("Waiting for event: '" + eventName + "' on " + target + ".");
|
||||
|
||||
let deferred = promise.defer();
|
||||
let count = 0;
|
||||
|
||||
for (let [add, remove] of [
|
||||
["addEventListener", "removeEventListener"],
|
||||
["addListener", "removeListener"],
|
||||
["on", "off"]
|
||||
]) {
|
||||
if ((add in target) && (remove in target)) {
|
||||
target[add](eventName, function onEvent(...aArgs) {
|
||||
if (++count == numTimes) {
|
||||
target[remove](eventName, onEvent, useCapture);
|
||||
deferred.resolve.apply(deferred, aArgs);
|
||||
}
|
||||
}, useCapture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a content -> chrome message on the message manager (the window
|
||||
* messagemanager is used).
|
||||
@ -284,22 +214,6 @@ function* hideTooltipAndWaitForRuleViewChanged(tooltip, view) {
|
||||
yield onModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for a new tab to open and return a promise that resolves when one
|
||||
* does and completes the load event.
|
||||
*
|
||||
* @return a promise that resolves to the tab object
|
||||
*/
|
||||
var waitForTab = Task.async(function* () {
|
||||
info("Waiting for a tab to open");
|
||||
yield once(gBrowser.tabContainer, "TabOpen");
|
||||
let tab = gBrowser.selectedTab;
|
||||
let browser = tab.linkedBrowser;
|
||||
yield once(browser, "load", true);
|
||||
info("The tab load completed");
|
||||
return tab;
|
||||
});
|
||||
|
||||
/**
|
||||
* Polls a given generator function waiting for it to return true.
|
||||
*
|
||||
@ -348,20 +262,6 @@ var getFontFamilyDataURL = Task.async(function* (font, nodeFront) {
|
||||
return dataURL;
|
||||
});
|
||||
|
||||
/**
|
||||
* Simulate the key input for the given input in the window.
|
||||
*
|
||||
* @param {String} input
|
||||
* The string value to input
|
||||
* @param {Window} win
|
||||
* The window containing the panel
|
||||
*/
|
||||
function synthesizeKeys(input, win) {
|
||||
for (let key of input.split("")) {
|
||||
EventUtils.synthesizeKey(key, {}, win);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DOMNode for a css rule in the rule-view that corresponds to the given
|
||||
* selector
|
||||
|
@ -12,11 +12,17 @@ support-files =
|
||||
doc_content_stylesheet_xul.css
|
||||
doc_frame_script.js
|
||||
head.js
|
||||
!/devtools/client/commandline/test/helpers.js
|
||||
!/devtools/client/inspector/test/head.js
|
||||
!/devtools/client/framework/test/shared-head.js
|
||||
!/devtools/client/shared/test/test-actor.js
|
||||
!/devtools/client/shared/test/test-actor-registry.js
|
||||
|
||||
[browser_styleinspector_context-menu-copy-color_01.js]
|
||||
[browser_styleinspector_context-menu-copy-color_02.js]
|
||||
[browser_styleinspector_context-menu-copy-urls.js]
|
||||
[browser_styleinspector_csslogic-content-stylesheets.js]
|
||||
skip-if = e10s && debug # Bug 1250058 (docshell leak when opening 2 toolboxes)
|
||||
[browser_styleinspector_output-parser.js]
|
||||
[browser_styleinspector_refresh_when_active.js]
|
||||
[browser_styleinspector_tooltip-background-image.js]
|
||||
|
@ -11,31 +11,20 @@ const TEST_URI = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
const TEST_CASES = [
|
||||
{
|
||||
viewName: "RuleView",
|
||||
initializer: openRuleView
|
||||
},
|
||||
{
|
||||
viewName: "ComputedView",
|
||||
initializer: openComputedView
|
||||
}
|
||||
];
|
||||
|
||||
add_task(function* () {
|
||||
// Test is slow on Linux EC2 instances - Bug 1137765
|
||||
requestLongerTimeout(2);
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
|
||||
for (let test of TEST_CASES) {
|
||||
yield testView(test);
|
||||
}
|
||||
let {inspector} = yield openInspector();
|
||||
yield testView("ruleview", inspector);
|
||||
yield testView("computedview", inspector);
|
||||
});
|
||||
|
||||
function* testView({viewName, initializer}) {
|
||||
info("Testing " + viewName);
|
||||
function* testView(viewId, inspector) {
|
||||
info("Testing " + viewId);
|
||||
|
||||
let {inspector, view} = yield initializer();
|
||||
yield inspector.sidebar.select(viewId);
|
||||
let view = inspector[viewId].view;
|
||||
yield selectNode("div", inspector);
|
||||
|
||||
testIsColorValueNode(view);
|
||||
|
@ -19,6 +19,7 @@ add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
yield testCopyToClipboard(inspector, view);
|
||||
yield testManualEdit(inspector, view);
|
||||
yield testColorPickerEdit(inspector, view);
|
||||
|
@ -39,24 +39,24 @@ add_task(function*() {
|
||||
|
||||
function* startTest() {
|
||||
info("Opening rule view");
|
||||
let ruleViewData = yield openRuleView();
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
info("Test valid background image URL in rule view");
|
||||
yield testCopyUrlToClipboard(ruleViewData, "data-uri", ".valid-background", TEST_DATA_URI);
|
||||
yield testCopyUrlToClipboard(ruleViewData, "url", ".valid-background", TEST_DATA_URI);
|
||||
yield testCopyUrlToClipboard({view, inspector}, "data-uri", ".valid-background", TEST_DATA_URI);
|
||||
yield testCopyUrlToClipboard({view, inspector}, "url", ".valid-background", TEST_DATA_URI);
|
||||
info("Test invalid background image URL in rue view");
|
||||
yield testCopyUrlToClipboard(ruleViewData, "data-uri", ".invalid-background", ERROR_MESSAGE);
|
||||
yield testCopyUrlToClipboard(ruleViewData, "url", ".invalid-background", INVALID_IMAGE_URI);
|
||||
yield testCopyUrlToClipboard({view, inspector}, "data-uri", ".invalid-background", ERROR_MESSAGE);
|
||||
yield testCopyUrlToClipboard({view, inspector}, "url", ".invalid-background", INVALID_IMAGE_URI);
|
||||
|
||||
info("Opening computed view");
|
||||
let computedViewData = yield openComputedView();
|
||||
view = selectComputedView(inspector);
|
||||
|
||||
info("Test valid background image URL in computed view");
|
||||
yield testCopyUrlToClipboard(computedViewData, "data-uri", ".valid-background", TEST_DATA_URI);
|
||||
yield testCopyUrlToClipboard(computedViewData, "url", ".valid-background", TEST_DATA_URI);
|
||||
yield testCopyUrlToClipboard({view, inspector}, "data-uri", ".valid-background", TEST_DATA_URI);
|
||||
yield testCopyUrlToClipboard({view, inspector}, "url", ".valid-background", TEST_DATA_URI);
|
||||
info("Test invalid background image URL in computed view");
|
||||
yield testCopyUrlToClipboard(computedViewData, "data-uri", ".invalid-background", ERROR_MESSAGE);
|
||||
yield testCopyUrlToClipboard(computedViewData, "url", ".invalid-background", INVALID_IMAGE_URI);
|
||||
yield testCopyUrlToClipboard({view, inspector}, "data-uri", ".invalid-background", ERROR_MESSAGE);
|
||||
yield testCopyUrlToClipboard({view, inspector}, "url", ".invalid-background", INVALID_IMAGE_URI);
|
||||
}
|
||||
|
||||
function* testCopyUrlToClipboard({view, inspector}, type, selector, expected) {
|
||||
|
@ -27,7 +27,7 @@ add_task(function*() {
|
||||
yield addTab(TEST_URI_HTML);
|
||||
let target = getNode("#target");
|
||||
|
||||
let {inspector} = yield openRuleView();
|
||||
let {inspector} = yield openInspector();
|
||||
yield selectNode("#target", inspector);
|
||||
|
||||
info("Checking stylesheets");
|
||||
@ -36,7 +36,7 @@ add_task(function*() {
|
||||
info("Checking authored stylesheets");
|
||||
yield addTab(TEST_URI_AUTHOR);
|
||||
|
||||
({inspector} = yield openRuleView());
|
||||
({inspector} = yield openInspector());
|
||||
target = getNode("#target");
|
||||
yield selectNode("#target", inspector);
|
||||
yield checkSheets(target);
|
||||
@ -46,7 +46,7 @@ add_task(function*() {
|
||||
allowXUL();
|
||||
yield addTab(TEST_URI_XUL);
|
||||
|
||||
({inspector} = yield openRuleView());
|
||||
({inspector} = yield openInspector());
|
||||
target = getNode("#target");
|
||||
yield selectNode("#target", inspector);
|
||||
|
||||
|
@ -14,6 +14,7 @@ const TEST_URI = `
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
yield selectNode("#one", inspector);
|
||||
|
||||
is(getRuleViewPropertyValue(view, "element", "color"), "red",
|
||||
@ -25,7 +26,7 @@ add_task(function*() {
|
||||
|
||||
info("Switching to the computed-view");
|
||||
let onComputedViewReady = inspector.once("computed-view-refreshed");
|
||||
yield openComputedView();
|
||||
selectComputedView(inspector);
|
||||
yield onComputedViewReady;
|
||||
|
||||
ok(getComputedViewPropertyValue(cView, "color"), "#F00",
|
||||
|
@ -43,7 +43,7 @@ add_task(function*() {
|
||||
|
||||
info("Switching over to the computed-view");
|
||||
let onComputedViewReady = inspector.once("computed-view-refreshed");
|
||||
({view} = yield openComputedView());
|
||||
view = selectComputedView(inspector);
|
||||
yield onComputedViewReady;
|
||||
|
||||
info("Testing that the background-image computed style has a tooltip too");
|
||||
|
@ -17,7 +17,7 @@ add_task(function*() {
|
||||
yield testRuleView(view, inspector);
|
||||
|
||||
info("Testing computed view tooltip closes on new selection");
|
||||
({view} = yield openComputedView());
|
||||
view = selectComputedView(inspector);
|
||||
yield testComputedView(view, inspector);
|
||||
});
|
||||
|
||||
|
@ -25,7 +25,7 @@ add_task(function*() {
|
||||
|
||||
info("Opening the computed view");
|
||||
let onComputedViewReady = inspector.once("computed-view-refreshed");
|
||||
({inspector, view} = yield openComputedView());
|
||||
view = selectComputedView(inspector);
|
||||
yield onComputedViewReady;
|
||||
|
||||
yield testComputedView(view, inspector.selection.nodeFront);
|
||||
|
@ -25,25 +25,27 @@ add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,background image tooltip test");
|
||||
content.document.body.innerHTML = PAGE_CONTENT;
|
||||
|
||||
yield testRuleViewUrls();
|
||||
yield testComputedViewUrls();
|
||||
let {inspector} = yield openInspector();
|
||||
yield testRuleViewUrls(inspector);
|
||||
yield testComputedViewUrls(inspector);
|
||||
});
|
||||
|
||||
function* testRuleViewUrls() {
|
||||
function* testRuleViewUrls(inspector) {
|
||||
info("Testing tooltips in the rule view");
|
||||
|
||||
let {view, inspector} = yield openRuleView();
|
||||
let view = selectRuleView(inspector);
|
||||
yield selectNode("h1", inspector);
|
||||
|
||||
let {valueSpan} = getRuleViewProperty(view, "h1", "background");
|
||||
yield performChecks(view, valueSpan);
|
||||
}
|
||||
|
||||
function* testComputedViewUrls() {
|
||||
function* testComputedViewUrls(inspector) {
|
||||
info("Testing tooltips in the computed view");
|
||||
|
||||
let {view, inspector} = yield openComputedView();
|
||||
yield inspector.once("computed-view-refreshed");
|
||||
let onComputedViewReady = inspector.once("computed-view-refreshed");
|
||||
let view = selectComputedView(inspector);
|
||||
yield onComputedViewReady;
|
||||
|
||||
let {valueSpan} = getComputedViewProperty(view, "background-image");
|
||||
|
||||
yield performChecks(view, valueSpan);
|
||||
|
@ -18,6 +18,7 @@ const TEST_URI = `
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
yield selectNode("#testElement", inspector);
|
||||
yield testRuleView(view, inspector.selection.nodeFront);
|
||||
});
|
||||
|
@ -20,6 +20,7 @@ const TYPE = "CssTransformHighlighter";
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
let overlay = view.highlighters;
|
||||
|
||||
ok(!overlay.highlighters[TYPE], "No highlighter exists in the rule-view");
|
||||
@ -32,7 +33,7 @@ add_task(function*() {
|
||||
"The same instance of highlighter is returned everytime in the rule-view");
|
||||
|
||||
let onComputedViewReady = inspector.once("computed-view-refreshed");
|
||||
let {view: cView} = yield openComputedView();
|
||||
let cView = selectComputedView(inspector);
|
||||
yield onComputedViewReady;
|
||||
overlay = cView.highlighters;
|
||||
|
||||
|
@ -38,7 +38,7 @@ add_task(function*() {
|
||||
yield onHighlighterShown;
|
||||
|
||||
let onComputedViewReady = inspector.once("computed-view-refreshed");
|
||||
let {view: cView} = yield openComputedView();
|
||||
let cView = selectComputedView(inspector);
|
||||
yield onComputedViewReady;
|
||||
hs = cView.highlighters;
|
||||
|
||||
|
@ -1,23 +1,19 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from ../../test/head.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
var Cu = Components.utils;
|
||||
var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
var {gDevTools} = require("devtools/client/framework/devtools");
|
||||
var {TargetFactory} = require("devtools/client/framework/target");
|
||||
var {CssRuleView, _ElementStyle} = require("devtools/client/inspector/rules/rules");
|
||||
var {CssLogic, CssSelector} = require("devtools/shared/inspector/css-logic");
|
||||
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
var promise = require("promise");
|
||||
var {editableField, getInplaceEditorForSpan: inplaceEditor} =
|
||||
require("devtools/client/shared/inplace-editor");
|
||||
var {console} = Cu.import("resource://gre/modules/Console.jsm", {});
|
||||
// Import the inspector's head.js first (which itself imports shared-head.js).
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
|
||||
this);
|
||||
|
||||
// All tests are asynchronous
|
||||
waitForExplicitFinish();
|
||||
var {CssRuleView} = require("devtools/client/inspector/rules/rules");
|
||||
var {CssLogic, CssSelector} = require("devtools/shared/inspector/css-logic");
|
||||
var {getInplaceEditorForSpan: inplaceEditor} =
|
||||
require("devtools/client/shared/inplace-editor");
|
||||
|
||||
const TEST_URL_ROOT =
|
||||
"http://example.com/browser/devtools/client/inspector/shared/test/";
|
||||
@ -26,28 +22,9 @@ const TEST_URL_ROOT_SSL =
|
||||
const ROOT_TEST_DIR = getRootDirectory(gTestPath);
|
||||
const FRAME_SCRIPT_URL = ROOT_TEST_DIR + "doc_frame_script.js";
|
||||
|
||||
// Auto clean-up when a test ends
|
||||
registerCleanupFunction(function*() {
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
yield gDevTools.closeToolbox(target);
|
||||
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
});
|
||||
|
||||
// Uncomment this pref to dump all devtools emitted events to the console.
|
||||
// Services.prefs.setBoolPref("devtools.dump.emit", true);
|
||||
|
||||
// Set the testing flag on gDevTools and reset it when the test ends
|
||||
DevToolsUtils.testing = true;
|
||||
registerCleanupFunction(() => DevToolsUtils.testing = false);
|
||||
|
||||
// Clean-up all prefs that might have been changed during a test run
|
||||
// (safer here because if the test fails, then the pref is never reverted)
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
|
||||
Services.prefs.clearUserPref("devtools.dump.emit");
|
||||
Services.prefs.clearUserPref("devtools.defaultColorUnit");
|
||||
});
|
||||
|
||||
@ -62,8 +39,9 @@ registerCleanupFunction(() => {
|
||||
*
|
||||
* add_task(function*() {
|
||||
* yield addTab(TEST_URI);
|
||||
* let {toolbox, inspector, view} = yield openComputedView();
|
||||
*
|
||||
* let {toolbox, inspector} = yield openInspector();
|
||||
* inspector.sidebar.select(viewId);
|
||||
* let view = inspector[viewId].view;
|
||||
* yield selectNode("#test", inspector);
|
||||
* yield someAsyncTestFunction(view);
|
||||
* });
|
||||
@ -92,263 +70,21 @@ registerCleanupFunction(() => {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add a new test tab in the browser and load the given url.
|
||||
*
|
||||
* @param {String} url
|
||||
* The url to be loaded in the new tab
|
||||
* @return a promise that resolves to the tab object when the url is loaded
|
||||
* The rule-view tests rely on a frame-script to be injected in the content test
|
||||
* page. So override the shared-head's addTab to load the frame script after the
|
||||
* tab was added.
|
||||
* FIXME: Refactor the rule-view tests to use the testActor instead of a frame
|
||||
* script, so they can run on remote targets too.
|
||||
*/
|
||||
function addTab(url) {
|
||||
info("Adding a new tab with URL: '" + url + "'");
|
||||
let def = promise.defer();
|
||||
|
||||
window.focus();
|
||||
|
||||
let tab = window.gBrowser.selectedTab = window.gBrowser.addTab(url);
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
info("Loading the helper frame script " + FRAME_SCRIPT_URL);
|
||||
browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
|
||||
|
||||
browser.addEventListener("load", function onload() {
|
||||
browser.removeEventListener("load", onload, true);
|
||||
info("URL '" + url + "' loading complete");
|
||||
|
||||
def.resolve(tab);
|
||||
}, true);
|
||||
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple DOM node accesor function that takes either a node or a string css
|
||||
* selector as argument and returns the corresponding node
|
||||
*
|
||||
* @param {String|DOMNode} nodeOrSelector
|
||||
* @return {DOMNode|CPOW} Note that in e10s mode a CPOW object is returned which
|
||||
* doesn't implement *all* of the DOMNode's properties
|
||||
*/
|
||||
function getNode(nodeOrSelector) {
|
||||
info("Getting the node for '" + nodeOrSelector + "'");
|
||||
return typeof nodeOrSelector === "string" ?
|
||||
content.document.querySelector(nodeOrSelector) :
|
||||
nodeOrSelector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the NodeFront for a given css selector, via the protocol
|
||||
*
|
||||
* @param {String} selector
|
||||
* @param {InspectorPanel} inspector
|
||||
* The instance of InspectorPanel currently loaded in the toolbox
|
||||
* @return {Promise} Resolves to the NodeFront instance
|
||||
*/
|
||||
function getNodeFront(selector, {walker}) {
|
||||
return walker.querySelector(walker.rootNode, selector);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the inspector's current selection to a node or to the first match of the
|
||||
* given css selector.
|
||||
*
|
||||
* @param {String|NodeFront} data
|
||||
* The node to select
|
||||
* @param {InspectorPanel} inspector
|
||||
* The instance of InspectorPanel currently loaded in the toolbox
|
||||
* @param {String} reason
|
||||
* Defaults to "test" which instructs the inspector not
|
||||
* to highlight the node upon selection
|
||||
* @return {Promise} Resolves when the inspector is updated with the new node
|
||||
*/
|
||||
var selectNode = Task.async(function*(data, inspector, reason="test") {
|
||||
info("Selecting the node for '" + data + "'");
|
||||
let nodeFront = data;
|
||||
if (!data._form) {
|
||||
nodeFront = yield getNodeFront(data, inspector);
|
||||
}
|
||||
let updated = inspector.once("inspector-updated");
|
||||
inspector.selection.setNodeFront(nodeFront, reason);
|
||||
yield updated;
|
||||
});
|
||||
|
||||
/**
|
||||
* Set the inspector's current selection to null so that no node is selected
|
||||
*
|
||||
* @param {InspectorPanel} inspector
|
||||
* The instance of InspectorPanel currently loaded in the toolbox
|
||||
* @return a promise that resolves when the inspector is updated
|
||||
*/
|
||||
function clearCurrentNodeSelection(inspector) {
|
||||
info("Clearing the current selection");
|
||||
let updated = inspector.once("inspector-updated");
|
||||
inspector.selection.setNodeFront(null);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible.
|
||||
*
|
||||
* @return a promise that resolves when the inspector is ready
|
||||
*/
|
||||
var openInspector = Task.async(function*() {
|
||||
info("Opening the inspector");
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
let inspector, toolbox;
|
||||
|
||||
// Checking if the toolbox and the inspector are already loaded
|
||||
// The inspector-updated event should only be waited for if the inspector
|
||||
// isn't loaded yet
|
||||
toolbox = gDevTools.getToolbox(target);
|
||||
if (toolbox) {
|
||||
inspector = toolbox.getPanel("inspector");
|
||||
if (inspector) {
|
||||
info("Toolbox and inspector already open");
|
||||
return {
|
||||
toolbox: toolbox,
|
||||
inspector: inspector
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
info("Opening the toolbox");
|
||||
toolbox = yield gDevTools.showToolbox(target, "inspector");
|
||||
yield waitForToolboxFrameFocus(toolbox);
|
||||
inspector = toolbox.getPanel("inspector");
|
||||
|
||||
info("Waiting for the inspector to update");
|
||||
if (inspector._updateProgress) {
|
||||
yield inspector.once("inspector-updated");
|
||||
}
|
||||
|
||||
return {
|
||||
toolbox: toolbox,
|
||||
inspector: inspector
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Wait for the toolbox frame to receive focus after it loads
|
||||
*
|
||||
* @param {Toolbox} toolbox
|
||||
* @return a promise that resolves when focus has been received
|
||||
*/
|
||||
function waitForToolboxFrameFocus(toolbox) {
|
||||
info("Making sure that the toolbox's frame is focused");
|
||||
let def = promise.defer();
|
||||
let win = toolbox.frame.contentWindow;
|
||||
waitForFocus(def.resolve, win);
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible, and the sidebar that
|
||||
* corresponds to the given id selected
|
||||
*
|
||||
* @return a promise that resolves when the inspector is ready and the sidebar
|
||||
* view is visible and ready
|
||||
*/
|
||||
var openInspectorSideBar = Task.async(function*(id) {
|
||||
let {toolbox, inspector} = yield openInspector();
|
||||
|
||||
info("Selecting the " + id + " sidebar");
|
||||
inspector.sidebar.select(id);
|
||||
|
||||
return {
|
||||
toolbox: toolbox,
|
||||
inspector: inspector,
|
||||
view: inspector[id].view
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible, and the computed-view
|
||||
* sidebar tab selected.
|
||||
*
|
||||
* @return a promise that resolves when the inspector is ready and the computed
|
||||
* view is visible and ready
|
||||
*/
|
||||
function openComputedView() {
|
||||
return openInspectorSideBar("computedview");
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible, and the rule-view
|
||||
* sidebar tab selected.
|
||||
*
|
||||
* @return a promise that resolves when the inspector is ready and the rule
|
||||
* view is visible and ready
|
||||
*/
|
||||
function openRuleView() {
|
||||
return openInspectorSideBar("ruleview");
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for eventName on target to be delivered a number of times.
|
||||
*
|
||||
* @param {Object} target
|
||||
* An observable object that either supports on/off or
|
||||
* addEventListener/removeEventListener
|
||||
* @param {String} eventName
|
||||
* @param {Number} numTimes
|
||||
* Number of deliveries to wait for.
|
||||
* @param {Boolean} useCapture
|
||||
* Optional, for addEventListener/removeEventListener
|
||||
* @return A promise that resolves when the event has been handled
|
||||
*/
|
||||
function waitForNEvents(target, eventName, numTimes, useCapture = false) {
|
||||
info("Waiting for event: '" + eventName + "' on " + target + ".");
|
||||
|
||||
let deferred = promise.defer();
|
||||
let count = 0;
|
||||
|
||||
for (let [add, remove] of [
|
||||
["addEventListener", "removeEventListener"],
|
||||
["addListener", "removeListener"],
|
||||
["on", "off"]
|
||||
]) {
|
||||
if ((add in target) && (remove in target)) {
|
||||
target[add](eventName, function onEvent(...aArgs) {
|
||||
if (++count == numTimes) {
|
||||
target[remove](eventName, onEvent, useCapture);
|
||||
deferred.resolve.apply(deferred, aArgs);
|
||||
}
|
||||
}, useCapture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for eventName on target.
|
||||
*
|
||||
* @param {Object} target
|
||||
* An observable object that either supports on/off or
|
||||
* addEventListener/removeEventListener
|
||||
* @param {String} eventName
|
||||
* @param {Boolean} useCapture
|
||||
* Optional, for addEventListener/removeEventListener
|
||||
* @return A promise that resolves when the event has been handled
|
||||
*/
|
||||
function once(target, eventName, useCapture=false) {
|
||||
return waitForNEvents(target, eventName, 1, useCapture);
|
||||
}
|
||||
|
||||
/**
|
||||
* This shouldn't be used in the tests, but is useful when writing new tests or
|
||||
* debugging existing tests in order to introduce delays in the test steps
|
||||
*
|
||||
* @param {Number} ms
|
||||
* The time to wait
|
||||
* @return A promise that resolves when the time is passed
|
||||
*/
|
||||
function wait(ms) {
|
||||
let def = promise.defer();
|
||||
content.setTimeout(def.resolve, ms);
|
||||
return def.promise;
|
||||
}
|
||||
var _addTab = addTab;
|
||||
addTab = function(url) {
|
||||
return _addTab(url).then(tab => {
|
||||
info("Loading the helper frame script " + FRAME_SCRIPT_URL);
|
||||
let browser = tab.linkedBrowser;
|
||||
browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
|
||||
return tab;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for a content -> chrome message on the message manager (the window
|
||||
@ -492,39 +228,6 @@ function assertHoverTooltipOn(tooltip, element) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for a new tab to open and return a promise that resolves when one
|
||||
* does and completes the load event.
|
||||
*
|
||||
* @return a promise that resolves to the tab object
|
||||
*/
|
||||
var waitForTab = Task.async(function*() {
|
||||
info("Waiting for a tab to open");
|
||||
yield once(gBrowser.tabContainer, "TabOpen");
|
||||
let tab = gBrowser.selectedTab;
|
||||
let browser = tab.linkedBrowser;
|
||||
yield once(browser, "load", true);
|
||||
info("The tab load completed");
|
||||
return tab;
|
||||
});
|
||||
|
||||
/**
|
||||
* @see SimpleTest.waitForClipboard
|
||||
*
|
||||
* @param {Function} setup
|
||||
* Function to execute before checking for the
|
||||
* clipboard content
|
||||
* @param {String|Boolean} expected
|
||||
* An expected string or validator function
|
||||
* @return a promise that resolves when the expected string has been found or
|
||||
* the validator function has returned true, rejects otherwise.
|
||||
*/
|
||||
function waitForClipboard(setup, expected) {
|
||||
let def = promise.defer();
|
||||
SimpleTest.waitForClipboard(expected, setup, def.resolve, def.reject);
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls a given function waiting for it to return true.
|
||||
*
|
||||
@ -554,36 +257,6 @@ function waitForSuccess(validatorFn, name="untitled") {
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new style tag containing the given style text and append it to the
|
||||
* document's head node
|
||||
*
|
||||
* @param {Document} doc
|
||||
* @param {String} style
|
||||
* @return {DOMNode} The newly created style node
|
||||
*/
|
||||
function addStyle(doc, style) {
|
||||
info("Adding a new style tag to the document with style content: " +
|
||||
style.substring(0, 50));
|
||||
let node = doc.createElement("style");
|
||||
node.setAttribute("type", "text/css");
|
||||
node.textContent = style;
|
||||
doc.getElementsByTagName("head")[0].appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the inspector's sidebar corresponding to the given id already
|
||||
* exists
|
||||
*
|
||||
* @param {InspectorPanel}
|
||||
* @param {String}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function hasSideBarTab(inspector, id) {
|
||||
return !!inspector.sidebar.getWindowForTab(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dataURL for the font family tooltip.
|
||||
*
|
||||
@ -602,20 +275,6 @@ var getFontFamilyDataURL = Task.async(function*(font, nodeFront) {
|
||||
return dataURL;
|
||||
});
|
||||
|
||||
/**
|
||||
* Simulate the key input for the given input in the window.
|
||||
*
|
||||
* @param {String} input
|
||||
* The string value to input
|
||||
* @param {Window} win
|
||||
* The window containing the panel
|
||||
*/
|
||||
function synthesizeKeys(input, win) {
|
||||
for (let key of input.split("")) {
|
||||
EventUtils.synthesizeKey(key, {}, win);
|
||||
}
|
||||
}
|
||||
|
||||
/* *********************************************
|
||||
* RULE-VIEW
|
||||
* *********************************************
|
||||
@ -921,77 +580,3 @@ function getComputedViewPropertyValue(view, name, propertyName) {
|
||||
return getComputedViewProperty(view, name, propertyName)
|
||||
.valueSpan.textContent;
|
||||
}
|
||||
|
||||
/* *********************************************
|
||||
* STYLE-EDITOR
|
||||
* *********************************************
|
||||
* Style-editor related utility functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Wait for the toolbox to emit the styleeditor-selected event and when done
|
||||
* wait for the stylesheet identified by href to be loaded in the stylesheet
|
||||
* editor
|
||||
*
|
||||
* @param {Toolbox} toolbox
|
||||
* @param {String} href
|
||||
* Optional, if not provided, wait for the first editor to be ready
|
||||
* @return a promise that resolves to the editor when the stylesheet editor is
|
||||
* ready
|
||||
*/
|
||||
function waitForStyleEditor(toolbox, href) {
|
||||
let def = promise.defer();
|
||||
|
||||
info("Waiting for the toolbox to switch to the styleeditor");
|
||||
toolbox.once("styleeditor-selected").then(() => {
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
ok(panel && panel.UI, "Styleeditor panel switched to front");
|
||||
|
||||
// A helper that resolves the promise once it receives an editor that
|
||||
// matches the expected href. Returns false if the editor was not correct.
|
||||
let gotEditor = (event, editor) => {
|
||||
let currentHref = editor.styleSheet.href;
|
||||
if (!href || (href && currentHref.endsWith(href))) {
|
||||
info("Stylesheet editor selected");
|
||||
panel.UI.off("editor-selected", gotEditor);
|
||||
|
||||
editor.getSourceEditor().then(sourceEditor => {
|
||||
info("Stylesheet editor fully loaded");
|
||||
def.resolve(sourceEditor);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
info("The editor was incorrect. Waiting for editor-selected event.");
|
||||
return false;
|
||||
};
|
||||
|
||||
// The expected editor may already be selected. Check the if the currently
|
||||
// selected editor is the expected one and if not wait for an
|
||||
// editor-selected event.
|
||||
if (!gotEditor("styleeditor-selected", panel.UI.selectedEditor)) {
|
||||
// The expected editor is not selected (yet). Wait for it.
|
||||
panel.UI.on("editor-selected", gotEditor);
|
||||
}
|
||||
});
|
||||
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the current page and wait for the inspector to be initialized after
|
||||
* the navigation
|
||||
*
|
||||
* @param {InspectorPanel} inspector
|
||||
* The instance of InspectorPanel currently loaded in the toolbox
|
||||
* @return a promise that resolves after page reload and inspector
|
||||
* initialization
|
||||
*/
|
||||
function reloadPage(inspector) {
|
||||
let onNewRoot = inspector.once("new-root");
|
||||
content.location.reload();
|
||||
return onNewRoot.then(() => {
|
||||
inspector.markup._waitForChildren();
|
||||
});
|
||||
}
|
||||
|
@ -137,6 +137,20 @@ var selectNode = Task.async(function*(selector, inspector, reason="test") {
|
||||
yield updated;
|
||||
});
|
||||
|
||||
/**
|
||||
* Set the inspector's current selection to null so that no node is selected
|
||||
*
|
||||
* @param {InspectorPanel} inspector
|
||||
* The instance of InspectorPanel currently loaded in the toolbox
|
||||
* @return a promise that resolves when the inspector is updated
|
||||
*/
|
||||
function clearCurrentNodeSelection(inspector) {
|
||||
info("Clearing the current selection");
|
||||
let updated = inspector.once("inspector-updated");
|
||||
inspector.selection.setNodeFront(null);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the inspector in a tab with given URL.
|
||||
* @param {string} url The URL to open.
|
||||
@ -210,12 +224,13 @@ var clickOnInspectMenuItem = Task.async(function*(testActor, selector) {
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible, and the one of the sidebar
|
||||
* tabs selected.
|
||||
* @param {String} id The ID of the sidebar tab to be opened
|
||||
* @param {String} hostType Optional hostType, as defined in Toolbox.HostType
|
||||
*
|
||||
* @param {String} id
|
||||
* The ID of the sidebar tab to be opened
|
||||
* @return a promise that resolves when the inspector is ready and the tab is
|
||||
* visible and ready
|
||||
*/
|
||||
var openInspectorSidebarTab = Task.async(function*(id, hostType) {
|
||||
var openInspectorSidebarTab = Task.async(function* (id) {
|
||||
let {toolbox, inspector, testActor} = yield openInspector();
|
||||
|
||||
info("Selecting the " + id + " sidebar");
|
||||
@ -228,6 +243,66 @@ var openInspectorSidebarTab = Task.async(function*(id, hostType) {
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible, and the rule-view
|
||||
* sidebar tab selected.
|
||||
*
|
||||
* @return a promise that resolves when the inspector is ready and the rule view
|
||||
* is visible and ready
|
||||
*/
|
||||
function openRuleView() {
|
||||
return openInspectorSidebarTab("ruleview").then(data => {
|
||||
return {
|
||||
toolbox: data.toolbox,
|
||||
inspector: data.inspector,
|
||||
testActor: data.testActor,
|
||||
view: data.inspector.ruleview.view
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible, and the computed-view
|
||||
* sidebar tab selected.
|
||||
*
|
||||
* @return a promise that resolves when the inspector is ready and the computed
|
||||
* view is visible and ready
|
||||
*/
|
||||
function openComputedView() {
|
||||
return openInspectorSidebarTab("computedview").then(data => {
|
||||
return {
|
||||
toolbox: data.toolbox,
|
||||
inspector: data.inspector,
|
||||
testActor: data.testActor,
|
||||
view: data.inspector.computedview.view
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the rule view sidebar tab on an already opened inspector panel.
|
||||
*
|
||||
* @param {InspectorPanel} inspector
|
||||
* The opened inspector panel
|
||||
* @return {CssRuleView} the rule view
|
||||
*/
|
||||
function selectRuleView(inspector) {
|
||||
inspector.sidebar.select("ruleview");
|
||||
return inspector.ruleview.view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the computed view sidebar tab on an already opened inspector panel.
|
||||
*
|
||||
* @param {InspectorPanel} inspector
|
||||
* The opened inspector panel
|
||||
* @return {CssComputedView} the computed view
|
||||
*/
|
||||
function selectComputedView(inspector) {
|
||||
inspector.sidebar.select("computedview");
|
||||
return inspector.computedview.view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the NodeFront for a node that matches a given css selector, via the
|
||||
* protocol.
|
||||
@ -672,3 +747,33 @@ function containsFocus(doc, container) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for a new tab to open and return a promise that resolves when one
|
||||
* does and completes the load event.
|
||||
*
|
||||
* @return a promise that resolves to the tab object
|
||||
*/
|
||||
var waitForTab = Task.async(function*() {
|
||||
info("Waiting for a tab to open");
|
||||
yield once(gBrowser.tabContainer, "TabOpen");
|
||||
let tab = gBrowser.selectedTab;
|
||||
let browser = tab.linkedBrowser;
|
||||
yield once(browser, "load", true);
|
||||
info("The tab load completed");
|
||||
return tab;
|
||||
});
|
||||
|
||||
/**
|
||||
* Simulate the key input for the given input in the window.
|
||||
*
|
||||
* @param {String} input
|
||||
* The string value to input
|
||||
* @param {Window} win
|
||||
* The window containing the panel
|
||||
*/
|
||||
function synthesizeKeys(input, win) {
|
||||
for (let key of input.split("")) {
|
||||
EventUtils.synthesizeKey(key, {}, win);
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +142,7 @@
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item:hover,
|
||||
.theme-light .tabs .tabs-menu-item:hover {
|
||||
background-color: rgba(170,170,170,.2);
|
||||
background-color: var(--toolbar-tab-hover);
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item.is-active,
|
||||
@ -161,7 +161,7 @@
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item:active:hover,
|
||||
.theme-light .tabs .tabs-menu-item:active:hover {
|
||||
background-color: rgba(170,170,170,.4);
|
||||
background-color: var(--toolbar-tab-hover-active);
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item.is-active,
|
||||
@ -190,7 +190,7 @@
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item:active:hover {
|
||||
background-color: hsla(206,37%,4%,.4);
|
||||
background-color: hsla(206, 37%, 4%, .4); /* --toolbar-tab-hover-active */
|
||||
}
|
||||
|
||||
.theme-dark .tabs .tabs-menu-item.is-active {
|
||||
|
@ -79,7 +79,7 @@
|
||||
margin: 1px 2px 1px 2px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background-color: rgba(170, 170, 170, .2); /* Splitter */
|
||||
background-color: rgba(170, 170, 170, .2); /* --toolbar-tab-hover */
|
||||
transition: background 0.05s ease-in-out;
|
||||
}
|
||||
|
||||
@ -90,5 +90,5 @@
|
||||
|
||||
.theme-dark .toolbar .btn:not([disabled]):hover:active,
|
||||
.theme-light .toolbar .btn:not([disabled]):hover:active {
|
||||
background: rgba(170, 170, 170, .4); /* Splitters */
|
||||
background: rgba(170, 170, 170, .4); /* --toolbar-tab-hover-active */
|
||||
}
|
||||
|
@ -11,17 +11,24 @@
|
||||
# A good criteria is the language in which you'd find the best
|
||||
# documentation on web development on the web.
|
||||
|
||||
# LOCALIZATION NOTE (responsive.title): the title displayed in the global
|
||||
# toolbar
|
||||
responsive.title=Responsive Design Mode
|
||||
# LOCALIZATION NOTE (responsive.editDeviceList): option displayed in the device
|
||||
# selector
|
||||
responsive.editDeviceList=Edit list…
|
||||
|
||||
# LOCALIZATION NOTE (responsive.exit): tooltip text of the exit button.
|
||||
responsive.exit=Close Responsive Design Mode
|
||||
|
||||
# LOCALIZATION NOTE (responsive.done): button text in the device list modal
|
||||
responsive.done=Done
|
||||
|
||||
# LOCALIZATION NOTE (responsive.noDeviceSelected): placeholder text for the
|
||||
# device selector
|
||||
responsive.noDeviceSelected=no device selected
|
||||
|
||||
# LOCALIZATION NOTE (responsive.title): the title displayed in the global
|
||||
# toolbar
|
||||
responsive.title=Responsive Design Mode
|
||||
|
||||
# LOCALIZATION NOTE (responsive.screenshot): tooltip of the screenshot button.
|
||||
responsive.screenshot=Take a screenshot of the viewport
|
||||
|
||||
|
@ -7,6 +7,8 @@
|
||||
const {
|
||||
ADD_DEVICE,
|
||||
ADD_DEVICE_TYPE,
|
||||
UPDATE_DEVICE_DISPLAYED,
|
||||
UPDATE_DEVICE_MODAL_OPEN,
|
||||
} = require("./index");
|
||||
|
||||
module.exports = {
|
||||
@ -26,4 +28,20 @@ module.exports = {
|
||||
};
|
||||
},
|
||||
|
||||
updateDeviceDisplayed(device, deviceType, displayed) {
|
||||
return {
|
||||
type: UPDATE_DEVICE_DISPLAYED,
|
||||
device,
|
||||
deviceType,
|
||||
displayed,
|
||||
};
|
||||
},
|
||||
|
||||
updateDeviceModalOpen(isOpen) {
|
||||
return {
|
||||
type: UPDATE_DEVICE_MODAL_OPEN,
|
||||
isOpen,
|
||||
};
|
||||
},
|
||||
|
||||
};
|
||||
|
@ -38,6 +38,12 @@ createEnum([
|
||||
// Indicates when the screenshot action ends.
|
||||
"TAKE_SCREENSHOT_END",
|
||||
|
||||
// Update the device display state in the device selector.
|
||||
"UPDATE_DEVICE_DISPLAYED",
|
||||
|
||||
// Update the device modal open state.
|
||||
"UPDATE_DEVICE_MODAL_OPEN",
|
||||
|
||||
], module.exports);
|
||||
|
||||
/**
|
||||
|
@ -6,21 +6,23 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const {
|
||||
TAKE_SCREENSHOT_START,
|
||||
TAKE_SCREENSHOT_END,
|
||||
} = require("./index");
|
||||
|
||||
const { getRect } = require("devtools/shared/layout/utils");
|
||||
const { getFormatStr } = require("../utils/l10n");
|
||||
const { getToplevelWindow } = require("sdk/window/utils");
|
||||
const { Task: { spawn, async } } = require("resource://gre/modules/Task.jsm");
|
||||
const { Task: { spawn } } = require("resource://gre/modules/Task.jsm");
|
||||
const e10s = require("../utils/e10s");
|
||||
|
||||
const BASE_URL = "resource://devtools/client/responsive.html";
|
||||
const audioCamera = new window.Audio(`${BASE_URL}/audio/camera-click.mp3`);
|
||||
|
||||
const animationFrame = () => new Promise(resolve => {
|
||||
window.requestAnimationFrame(resolve);
|
||||
});
|
||||
|
||||
function getFileName() {
|
||||
let date = new Date();
|
||||
let month = ("0" + (date.getMonth() + 1)).substr(-2);
|
||||
@ -33,17 +35,9 @@ function getFileName() {
|
||||
}
|
||||
|
||||
function createScreenshotFor(node) {
|
||||
let { top, left, width, height } = getRect(window, node, window);
|
||||
let mm = node.frameLoader.messageManager;
|
||||
|
||||
const canvas = document.createElementNS(HTML_NS, "canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const ratio = window.devicePixelRatio;
|
||||
canvas.width = width * ratio;
|
||||
canvas.height = height * ratio;
|
||||
ctx.scale(ratio, ratio);
|
||||
ctx.drawWindow(window, left, top, width, height, "#fff");
|
||||
|
||||
return canvas.toDataURL("image/png", "");
|
||||
return e10s.request(mm, "RequestScreenshot");
|
||||
}
|
||||
|
||||
function saveToFile(data, filename) {
|
||||
@ -73,17 +67,16 @@ module.exports = {
|
||||
|
||||
// Waiting the next repaint, to ensure the react components
|
||||
// can be properly render after the action dispatched above
|
||||
window.requestAnimationFrame(async(function* () {
|
||||
let iframe = document.querySelector("iframe");
|
||||
let data = createScreenshotFor(iframe);
|
||||
yield animationFrame();
|
||||
|
||||
simulateCameraEffects(iframe);
|
||||
let iframe = document.querySelector("iframe");
|
||||
let data = yield createScreenshotFor(iframe);
|
||||
|
||||
yield saveToFile(data, getFileName());
|
||||
simulateCameraEffects(iframe);
|
||||
|
||||
dispatch({ type: TAKE_SCREENSHOT_END });
|
||||
}));
|
||||
yield saveToFile(data, getFileName());
|
||||
|
||||
dispatch({ type: TAKE_SCREENSHOT_END });
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -10,15 +10,21 @@ const { createClass, createFactory, PropTypes, DOM: dom } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const {
|
||||
updateDeviceDisplayed,
|
||||
updateDeviceModalOpen,
|
||||
} = require("./actions/devices");
|
||||
const {
|
||||
changeDevice,
|
||||
resizeViewport,
|
||||
rotateViewport
|
||||
} = require("./actions/viewports");
|
||||
const { takeScreenshot } = require("./actions/screenshot");
|
||||
const Types = require("./types");
|
||||
const Viewports = createFactory(require("./components/viewports"));
|
||||
const DeviceModal = createFactory(require("./components/device-modal"));
|
||||
const GlobalToolbar = createFactory(require("./components/global-toolbar"));
|
||||
const Viewports = createFactory(require("./components/viewports"));
|
||||
const { updateDeviceList } = require("./devices");
|
||||
const Types = require("./types");
|
||||
|
||||
let App = createClass({
|
||||
propTypes: {
|
||||
@ -46,6 +52,10 @@ let App = createClass({
|
||||
}, "*");
|
||||
},
|
||||
|
||||
onDeviceListUpdate(devices) {
|
||||
updateDeviceList(devices);
|
||||
},
|
||||
|
||||
onExit() {
|
||||
window.postMessage({ type: "exit" }, "*");
|
||||
},
|
||||
@ -62,6 +72,14 @@ let App = createClass({
|
||||
this.props.dispatch(takeScreenshot());
|
||||
},
|
||||
|
||||
onUpdateDeviceDisplayed(device, deviceType, displayed) {
|
||||
this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed));
|
||||
},
|
||||
|
||||
onUpdateDeviceModalOpen(isOpen) {
|
||||
this.props.dispatch(updateDeviceModalOpen(isOpen));
|
||||
},
|
||||
|
||||
render() {
|
||||
let {
|
||||
devices,
|
||||
@ -74,10 +92,13 @@ let App = createClass({
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onDeviceListUpdate,
|
||||
onExit,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onScreenshot,
|
||||
onUpdateDeviceDisplayed,
|
||||
onUpdateDeviceModalOpen,
|
||||
} = this;
|
||||
|
||||
return dom.div(
|
||||
@ -99,6 +120,13 @@ let App = createClass({
|
||||
onContentResize,
|
||||
onRotateViewport,
|
||||
onResizeViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
}),
|
||||
DeviceModal({
|
||||
devices,
|
||||
onDeviceListUpdate,
|
||||
onUpdateDeviceDisplayed,
|
||||
onUpdateDeviceModalOpen,
|
||||
})
|
||||
);
|
||||
},
|
||||
|
@ -13,7 +13,7 @@ const { DOM: dom, createClass, addons, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
const Types = require("../types");
|
||||
const { waitForMessage } = require("../utils/e10s");
|
||||
const e10s = require("../utils/e10s");
|
||||
|
||||
module.exports = createClass({
|
||||
/**
|
||||
@ -45,9 +45,9 @@ module.exports = createClass({
|
||||
// quite the same timing as when we _set_ a new size around the browser,
|
||||
// since it still needs to do async work before the content is actually
|
||||
// resized to match.
|
||||
mm.addMessageListener("ResponsiveMode:OnContentResize", onContentResize);
|
||||
e10s.on(mm, "OnContentResize", onContentResize);
|
||||
|
||||
let ready = waitForMessage(mm, "ResponsiveMode:ChildScriptReady");
|
||||
let ready = e10s.once(mm, "ChildScriptReady");
|
||||
mm.loadFrameScript("resource://devtools/client/responsivedesign/" +
|
||||
"responsivedesign-child.js", true);
|
||||
yield ready;
|
||||
@ -55,13 +55,12 @@ module.exports = createClass({
|
||||
let browserWindow = getToplevelWindow(window);
|
||||
let requiresFloatingScrollbars =
|
||||
!browserWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
|
||||
let started = waitForMessage(mm, "ResponsiveMode:Start:Done");
|
||||
mm.sendAsyncMessage("ResponsiveMode:Start", {
|
||||
|
||||
yield e10s.request(mm, "Start", {
|
||||
requiresFloatingScrollbars,
|
||||
// Tests expect events on resize to yield on various size changes
|
||||
notifyOnResize: DevToolsUtils.testing,
|
||||
});
|
||||
yield started;
|
||||
|
||||
// manager.js waits for this signal before allowing browser tests to start
|
||||
this.props.onBrowserMounted();
|
||||
@ -71,8 +70,8 @@ module.exports = createClass({
|
||||
let { onContentResize } = this;
|
||||
let browser = this.refs.browserContainer.querySelector("iframe.browser");
|
||||
let mm = browser.frameLoader.messageManager;
|
||||
mm.removeMessageListener("ResponsiveMode:OnContentResize", onContentResize);
|
||||
mm.sendAsyncMessage("ResponsiveMode:Stop");
|
||||
e10s.off(mm, "OnContentResize", onContentResize);
|
||||
e10s.emit(mm, "Stop");
|
||||
},
|
||||
|
||||
onContentResize(msg) {
|
||||
|
139
devtools/client/responsive.html/components/device-modal.js
Normal file
139
devtools/client/responsive.html/components/device-modal.js
Normal file
@ -0,0 +1,139 @@
|
||||
/* 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 { DOM: dom, createClass, PropTypes, addons } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const { getStr } = require("../utils/l10n");
|
||||
const Types = require("../types");
|
||||
|
||||
module.exports = createClass({
|
||||
propTypes: {
|
||||
devices: PropTypes.shape(Types.devices).isRequired,
|
||||
onDeviceListUpdate: PropTypes.func.isRequired,
|
||||
onUpdateDeviceDisplayed: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
displayName: "DeviceModal",
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
getInitialState() {
|
||||
return {};
|
||||
},
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
let {
|
||||
devices,
|
||||
} = nextProps;
|
||||
|
||||
for (let type of devices.types) {
|
||||
for (let device of devices[type]) {
|
||||
this.setState({
|
||||
[device.name]: device.displayed,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onDeviceCheckboxClick({ target }) {
|
||||
this.setState({
|
||||
[target.value]: !this.state[target.value]
|
||||
});
|
||||
},
|
||||
|
||||
onDeviceModalSubmit() {
|
||||
let {
|
||||
devices,
|
||||
onDeviceListUpdate,
|
||||
onUpdateDeviceDisplayed,
|
||||
onUpdateDeviceModalOpen,
|
||||
} = this.props;
|
||||
|
||||
let displayedDeviceList = [];
|
||||
|
||||
for (let type of devices.types) {
|
||||
for (let device of devices[type]) {
|
||||
if (this.state[device.name] != device.displayed) {
|
||||
onUpdateDeviceDisplayed(device, type, this.state[device.name]);
|
||||
}
|
||||
|
||||
if (this.state[device.name]) {
|
||||
displayedDeviceList.push(device.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDeviceListUpdate(displayedDeviceList);
|
||||
onUpdateDeviceModalOpen(false);
|
||||
},
|
||||
|
||||
render() {
|
||||
let {
|
||||
devices,
|
||||
onUpdateDeviceModalOpen,
|
||||
} = this.props;
|
||||
|
||||
let modalClass = "device-modal container";
|
||||
|
||||
if (!devices.isModalOpen) {
|
||||
modalClass += " hidden";
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: modalClass,
|
||||
},
|
||||
dom.button({
|
||||
id: "device-close-button",
|
||||
className: "toolbar-button devtools-button",
|
||||
onClick: () => onUpdateDeviceModalOpen(false),
|
||||
}),
|
||||
dom.div(
|
||||
{
|
||||
className: "device-modal-content",
|
||||
},
|
||||
devices.types.map(type => {
|
||||
return dom.div(
|
||||
{
|
||||
className: "device-type",
|
||||
key: type,
|
||||
},
|
||||
dom.header(
|
||||
{
|
||||
className: "device-header",
|
||||
},
|
||||
type
|
||||
),
|
||||
devices[type].map(device => {
|
||||
return dom.label(
|
||||
{
|
||||
className: "device-label",
|
||||
key: device.name,
|
||||
},
|
||||
dom.input({
|
||||
className: "device-input-checkbox",
|
||||
type: "checkbox",
|
||||
value: device.name,
|
||||
checked: this.state[device.name],
|
||||
onChange: this.onDeviceCheckboxClick,
|
||||
}),
|
||||
device.name
|
||||
);
|
||||
})
|
||||
);
|
||||
})
|
||||
),
|
||||
dom.button(
|
||||
{
|
||||
id: "device-submit-button",
|
||||
onClick: this.onDeviceModalSubmit,
|
||||
},
|
||||
getStr("responsive.done")
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
@ -9,6 +9,7 @@ const { DOM: dom, createClass, PropTypes, addons } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
const Types = require("../types");
|
||||
const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL";
|
||||
|
||||
module.exports = createClass({
|
||||
propTypes: {
|
||||
@ -16,6 +17,7 @@ module.exports = createClass({
|
||||
selectedDevice: PropTypes.string.isRequired,
|
||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
displayName: "DeviceSelector",
|
||||
@ -27,8 +29,14 @@ module.exports = createClass({
|
||||
devices,
|
||||
onChangeViewportDevice,
|
||||
onResizeViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
} = this.props;
|
||||
|
||||
if (target.value === OPEN_DEVICE_MODAL_VALUE) {
|
||||
onUpdateDeviceModalOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let type of devices.types) {
|
||||
for (let device of devices[type]) {
|
||||
if (device.name === target.value) {
|
||||
@ -50,7 +58,9 @@ module.exports = createClass({
|
||||
let options = [];
|
||||
for (let type of devices.types) {
|
||||
for (let device of devices[type]) {
|
||||
options.push(device);
|
||||
if (device.displayed) {
|
||||
options.push(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +85,10 @@ module.exports = createClass({
|
||||
key: device.name,
|
||||
value: device.name,
|
||||
}, device.name);
|
||||
})
|
||||
}),
|
||||
dom.option({
|
||||
value: OPEN_DEVICE_MODAL_VALUE,
|
||||
}, getStr("responsive.editDeviceList"))
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -30,7 +30,7 @@ module.exports = createClass({
|
||||
return dom.header(
|
||||
{
|
||||
id: "global-toolbar",
|
||||
className: "toolbar",
|
||||
className: "container",
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
DevToolsModules(
|
||||
'browser.js',
|
||||
'device-modal.js',
|
||||
'device-selector.js',
|
||||
'global-toolbar.js',
|
||||
'resizable-viewport.js',
|
||||
|
@ -28,6 +28,7 @@ module.exports = createClass({
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
displayName: "ResizableViewport",
|
||||
@ -123,6 +124,7 @@ module.exports = createClass({
|
||||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
} = this.props;
|
||||
|
||||
let resizeHandleClass = "viewport-resize-handle";
|
||||
@ -145,6 +147,7 @@ module.exports = createClass({
|
||||
onChangeViewportDevice,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
}),
|
||||
dom.div(
|
||||
{
|
||||
|
@ -17,6 +17,7 @@ module.exports = createClass({
|
||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
displayName: "ViewportToolbar",
|
||||
@ -30,17 +31,19 @@ module.exports = createClass({
|
||||
onChangeViewportDevice,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
} = this.props;
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "toolbar viewport-toolbar",
|
||||
className: "viewport-toolbar container",
|
||||
},
|
||||
DeviceSelector({
|
||||
devices,
|
||||
selectedDevice,
|
||||
onChangeViewportDevice,
|
||||
onResizeViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
}),
|
||||
dom.button({
|
||||
className: "viewport-rotate-button toolbar-button devtools-button",
|
||||
|
@ -22,6 +22,7 @@ module.exports = createClass({
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
displayName: "Viewport",
|
||||
@ -59,8 +60,9 @@ module.exports = createClass({
|
||||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
onContentResize,
|
||||
onBrowserMounted,
|
||||
onContentResize,
|
||||
onUpdateDeviceModalOpen,
|
||||
} = this.props;
|
||||
|
||||
let {
|
||||
@ -83,6 +85,7 @@ module.exports = createClass({
|
||||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
}),
|
||||
ViewportDimension({
|
||||
viewport,
|
||||
|
@ -21,6 +21,7 @@ module.exports = createClass({
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
displayName: "Viewports",
|
||||
@ -36,6 +37,7 @@ module.exports = createClass({
|
||||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
} = this.props;
|
||||
|
||||
return dom.div(
|
||||
@ -54,6 +56,7 @@ module.exports = createClass({
|
||||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
81
devtools/client/responsive.html/devices.js
Normal file
81
devtools/client/responsive.html/devices.js
Normal file
@ -0,0 +1,81 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Services = require("Services");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { GetDevices } = require("devtools/client/shared/devices");
|
||||
const { addDevice, addDeviceType } = require("./actions/devices");
|
||||
|
||||
const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";
|
||||
|
||||
/**
|
||||
* Get the device catalog and load the devices onto the store.
|
||||
*
|
||||
* @param {Function} dispatch
|
||||
* Action dispatch function
|
||||
*/
|
||||
let initDevices = Task.async(function* (dispatch) {
|
||||
let deviceList = loadDeviceList();
|
||||
let devices = yield GetDevices();
|
||||
|
||||
for (let type of devices.TYPES) {
|
||||
dispatch(addDeviceType(type));
|
||||
for (let device of devices[type]) {
|
||||
if (device.os == "fxos") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let newDevice = Object.assign({}, device, {
|
||||
displayed: deviceList.includes(device.name) ?
|
||||
true :
|
||||
!!device.featured,
|
||||
});
|
||||
|
||||
if (newDevice.displayed) {
|
||||
deviceList.push(newDevice.name);
|
||||
}
|
||||
|
||||
dispatch(addDevice(newDevice, type));
|
||||
}
|
||||
}
|
||||
|
||||
updateDeviceList(deviceList);
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns an array containing the user preference of displayed devices.
|
||||
*
|
||||
* @return {Array} containing the device names that are to be displayed in the
|
||||
* device catalog.
|
||||
*/
|
||||
function loadDeviceList() {
|
||||
let deviceList = [];
|
||||
|
||||
if (Services.prefs.prefHasUserValue(DISPLAYED_DEVICES_PREF)) {
|
||||
try {
|
||||
deviceList = JSON.parse(Services.prefs.getCharPref(
|
||||
DISPLAYED_DEVICES_PREF));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
return deviceList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the displayed device list preference with the given device list.
|
||||
*
|
||||
* @param {Array} devices
|
||||
* Array of device names that are displayed in the device catalog.
|
||||
*/
|
||||
function updateDeviceList(devices) {
|
||||
Services.prefs.setCharPref(DISPLAYED_DEVICES_PREF, JSON.stringify(devices));
|
||||
}
|
||||
|
||||
exports.initDevices = initDevices;
|
||||
exports.loadDeviceList = loadDeviceList;
|
||||
exports.updateDeviceList = updateDeviceList;
|
@ -6,7 +6,9 @@
|
||||
*/
|
||||
|
||||
.theme-light {
|
||||
--box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26);
|
||||
--rdm-box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26);
|
||||
--submit-button-active-background-color: rgba(0,0,0,0.12);
|
||||
--submit-button-active-color: var(--theme-body-color);
|
||||
--viewport-active-color: var(--theme-body-color);
|
||||
--viewport-selection-arrow: url("./images/select-arrow.svg#light");
|
||||
--viewport-selection-arrow-selected:
|
||||
@ -14,7 +16,9 @@
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
--box-shadow: 0 4px 4px 0 rgba(105, 105, 105, 0.26);
|
||||
--rdm-box-shadow: 0 4px 4px 0 rgba(105, 105, 105, 0.26);
|
||||
--submit-button-active-background-color: var(--toolbar-tab-hover-active);
|
||||
--submit-button-active-color: var(--theme-selection-color);
|
||||
--viewport-active-color: var(--theme-selection-color);
|
||||
--viewport-selection-arrow: url("./images/select-arrow.svg#dark");
|
||||
--viewport-selection-arrow-selected:
|
||||
@ -51,10 +55,10 @@ html, body {
|
||||
}
|
||||
|
||||
/**
|
||||
* Common style for toolbars and toolbar buttons
|
||||
* Common style for containers and toolbar buttons
|
||||
*/
|
||||
|
||||
.toolbar {
|
||||
.container {
|
||||
background-color: var(--theme-toolbar-background);
|
||||
border: 1px solid var(--theme-splitter-color);
|
||||
}
|
||||
@ -80,7 +84,7 @@ html, body {
|
||||
#global-toolbar {
|
||||
color: var(--theme-body-color-alt);
|
||||
border-radius: 2px;
|
||||
box-shadow: var(--box-shadow);
|
||||
box-shadow: var(--rdm-box-shadow);
|
||||
margin: 30px 0;
|
||||
padding: 4px 5px;
|
||||
display: inline-flex;
|
||||
@ -142,7 +146,7 @@ html, body {
|
||||
|
||||
.resizable-viewport {
|
||||
border: 1px solid var(--theme-splitter-color);
|
||||
box-shadow: var(--box-shadow);
|
||||
box-shadow: var(--rdm-box-shadow);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@ -308,3 +312,88 @@ html, body {
|
||||
.viewport-dimension-separator {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Device Modal
|
||||
*/
|
||||
|
||||
.device-modal {
|
||||
border-radius: 2px;
|
||||
box-shadow: var(--rdm-box-shadow);
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 642px;
|
||||
height: 612px;
|
||||
}
|
||||
|
||||
.device-modal.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.device-modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
overflow: auto;
|
||||
height: 550px;
|
||||
width: 600px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#device-close-button,
|
||||
#device-close-button::before {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
#device-close-button::before {
|
||||
background-image: url("./images/close.svg");
|
||||
margin: -6px 0 0 -6px;
|
||||
}
|
||||
|
||||
.device-type {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.device-header {
|
||||
font-weight: bold;
|
||||
text-transform: capitalize;
|
||||
padding: 0 0 3px 23px;
|
||||
}
|
||||
|
||||
.device-label {
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.device-input-checkbox {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#device-submit-button {
|
||||
background-color: var(--theme-tab-toolbar-background);
|
||||
border-width: 1px 0 0 0;
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
border-top-color: var(--theme-splitter-color);
|
||||
color: var(--theme-body-color);
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#device-submit-button:hover {
|
||||
background-color: var(--toolbar-tab-hover);
|
||||
}
|
||||
|
||||
#device-submit-button:hover:active {
|
||||
background-color: var(--submit-button-active-background-color);
|
||||
color: var(--submit-button-active-color);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ const { require } = BrowserLoader({
|
||||
baseURI: "resource://devtools/client/responsive.html/",
|
||||
window: this
|
||||
});
|
||||
const { GetDevices } = require("devtools/client/shared/devices");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const Telemetry = require("devtools/client/shared/telemetry");
|
||||
const { loadSheet } = require("sdk/stylesheet/utils");
|
||||
|
||||
@ -22,9 +22,9 @@ const { createFactory, createElement } =
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const { Provider } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const { initDevices } = require("./devices");
|
||||
const App = createFactory(require("./app"));
|
||||
const Store = require("./store");
|
||||
const { addDevice, addDeviceType } = require("./actions/devices");
|
||||
const { changeLocation } = require("./actions/location");
|
||||
const { addViewport, resizeViewport } = require("./actions/viewports");
|
||||
|
||||
@ -34,7 +34,7 @@ let bootstrap = {
|
||||
|
||||
store: null,
|
||||
|
||||
init() {
|
||||
init: Task.async(function* () {
|
||||
// Load a special UA stylesheet to reset certain styles such as dropdown
|
||||
// lists.
|
||||
loadSheet(window,
|
||||
@ -42,11 +42,11 @@ let bootstrap = {
|
||||
"agent");
|
||||
this.telemetry.toolOpened("responsive");
|
||||
let store = this.store = Store();
|
||||
yield initDevices(this.dispatch.bind(this));
|
||||
let provider = createElement(Provider, { store }, App());
|
||||
ReactDOM.render(provider, document.querySelector("#root"));
|
||||
this.initDevices();
|
||||
window.postMessage({ type: "init" }, "*");
|
||||
},
|
||||
}),
|
||||
|
||||
destroy() {
|
||||
this.store = null;
|
||||
@ -69,19 +69,6 @@ let bootstrap = {
|
||||
this.store.dispatch(action);
|
||||
},
|
||||
|
||||
initDevices() {
|
||||
GetDevices().then(devices => {
|
||||
for (let type of devices.TYPES) {
|
||||
this.dispatch(addDeviceType(type));
|
||||
for (let device of devices[type]) {
|
||||
if (device.os != "fxos") {
|
||||
this.dispatch(addDevice(device, type));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
window.addEventListener("load", function onLoad() {
|
||||
|
@ -16,6 +16,7 @@ DIRS += [
|
||||
DevToolsModules(
|
||||
'app.js',
|
||||
'constants.js',
|
||||
'devices.js',
|
||||
'index.css',
|
||||
'manager.js',
|
||||
'reducers.js',
|
||||
|
@ -7,10 +7,13 @@
|
||||
const {
|
||||
ADD_DEVICE,
|
||||
ADD_DEVICE_TYPE,
|
||||
UPDATE_DEVICE_DISPLAYED,
|
||||
UPDATE_DEVICE_MODAL_OPEN,
|
||||
} = require("../actions/index");
|
||||
|
||||
const INITIAL_DEVICES = {
|
||||
types: [],
|
||||
isModalOpen: false,
|
||||
};
|
||||
|
||||
let reducers = {
|
||||
@ -28,6 +31,26 @@ let reducers = {
|
||||
});
|
||||
},
|
||||
|
||||
[UPDATE_DEVICE_DISPLAYED](devices, { device, deviceType, displayed }) {
|
||||
let newDevices = devices[deviceType].map(d => {
|
||||
if (d == device) {
|
||||
d.displayed = displayed;
|
||||
}
|
||||
|
||||
return d;
|
||||
});
|
||||
|
||||
return Object.assign({}, devices, {
|
||||
[deviceType]: newDevices,
|
||||
});
|
||||
},
|
||||
|
||||
[UPDATE_DEVICE_MODAL_OPEN](devices, { isOpen }) {
|
||||
return Object.assign({}, devices, {
|
||||
isModalOpen: isOpen,
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = function(devices = INITIAL_DEVICES, action) {
|
||||
|
@ -9,6 +9,8 @@ support-files =
|
||||
!/devtools/client/framework/test/shared-head.js
|
||||
!/devtools/client/framework/test/shared-redux-head.js
|
||||
|
||||
[browser_device_modal_exit.js]
|
||||
[browser_device_modal_submit.js]
|
||||
[browser_device_width.js]
|
||||
[browser_exit_button.js]
|
||||
[browser_mouse_resize.js]
|
||||
|
@ -0,0 +1,38 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test submitting display device changes on the device modal
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8,";
|
||||
|
||||
addRDMTask(TEST_URL, function* ({ ui }) {
|
||||
let { store, document } = ui.toolWindow;
|
||||
let modal = document.querySelector(".device-modal");
|
||||
let closeButton = document.querySelector("#device-close-button");
|
||||
|
||||
// Wait until the viewport has been added
|
||||
yield waitUntilState(store, state => state.viewports.length == 1);
|
||||
|
||||
openDeviceModal(ui);
|
||||
|
||||
let deviceListBefore = loadDeviceList();
|
||||
|
||||
info("Check the first unchecked device and exit the modal.");
|
||||
let uncheckedCb = [...document.querySelectorAll(".device-input-checkbox")]
|
||||
.filter(cb => !cb.checked)[0];
|
||||
let value = uncheckedCb.value;
|
||||
uncheckedCb.click();
|
||||
closeButton.click();
|
||||
|
||||
ok(modal.classList.contains("hidden"),
|
||||
"The device modal is hidden on exit.");
|
||||
|
||||
info("Check that the device list remains unchanged after exitting.");
|
||||
let deviceListAfter = loadDeviceList();
|
||||
is(deviceListBefore.length, deviceListAfter.length,
|
||||
"Got expected number of displayed devices.");
|
||||
ok(!deviceListAfter.includes(value),
|
||||
value + " was not added to displayed device list.");
|
||||
});
|
@ -0,0 +1,59 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test submitting display device changes on the device modal
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8,";
|
||||
|
||||
addRDMTask(TEST_URL, function* ({ ui }) {
|
||||
let { store, document } = ui.toolWindow;
|
||||
let modal = document.querySelector(".device-modal");
|
||||
let select = document.querySelector(".viewport-device-selector");
|
||||
let submitButton = document.querySelector("#device-submit-button");
|
||||
|
||||
// Wait until the viewport has been added
|
||||
yield waitUntilState(store, state => state.viewports.length == 1);
|
||||
|
||||
openDeviceModal(ui);
|
||||
|
||||
info("Checking displayed device checkboxes are checked in the device modal.");
|
||||
let checkedCbs = [...document.querySelectorAll(".device-input-checkbox")]
|
||||
.filter(cb => cb.checked);
|
||||
let deviceList = loadDeviceList();
|
||||
|
||||
is(deviceList.length, checkedCbs.length,
|
||||
"Got expected number of displayed devices.");
|
||||
|
||||
for (let cb of checkedCbs) {
|
||||
ok(deviceList.includes(cb.value), cb.value + " is correctly checked.");
|
||||
}
|
||||
|
||||
info("Check the first unchecked device and submit new device list.");
|
||||
let uncheckedCb = [...document.querySelectorAll(".device-input-checkbox")]
|
||||
.filter(cb => !cb.checked)[0];
|
||||
let value = uncheckedCb.value;
|
||||
uncheckedCb.click();
|
||||
submitButton.click();
|
||||
|
||||
ok(modal.classList.contains("hidden"),
|
||||
"The device modal is hidden on submit.");
|
||||
|
||||
info("Checking new device is added to the displayed device list.");
|
||||
deviceList = loadDeviceList();
|
||||
ok(deviceList.includes(value), value + " added to displayed device list.");
|
||||
|
||||
info("Checking new device is added to the device selector.");
|
||||
let options = [...select.options];
|
||||
is(options.length - 2, deviceList.length,
|
||||
"Got expected number of devices in device selector.");
|
||||
ok(options.filter(o => o.value === value)[0],
|
||||
value + " added to the device selector.");
|
||||
|
||||
info("Reopen device modal and check new device is correctly checked");
|
||||
openDeviceModal(ui);
|
||||
ok([...document.querySelectorAll(".device-input-checkbox")]
|
||||
.filter(cb => cb.checked && cb.value === value)[0],
|
||||
value + " is checked in the device modal.");
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"TYPES": [ "phones" ],
|
||||
"TYPES": [ "phones", "tablets", "laptops", "televisions", "consoles", "watches" ],
|
||||
"phones": [
|
||||
{
|
||||
"name": "Firefox OS Flame",
|
||||
@ -20,6 +20,632 @@
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
},
|
||||
{
|
||||
"name": "Alcatel One Touch Fire C",
|
||||
"width": 320,
|
||||
"height": 480,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; ALCATELOneTouch4019X; rv:28.0) Gecko/28.0 Firefox/28.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
},
|
||||
{
|
||||
"name": "Alcatel One Touch Fire E",
|
||||
"width": 320,
|
||||
"height": 480,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; ALCATELOneTouch6015X; rv:32.0) Gecko/32.0 Firefox/32.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
},
|
||||
{
|
||||
"name": "Apple iPhone 4",
|
||||
"width": 320,
|
||||
"height": 480,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_2_1 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "ios"
|
||||
},
|
||||
{
|
||||
"name": "Apple iPhone 5",
|
||||
"width": 320,
|
||||
"height": 568,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "ios"
|
||||
},
|
||||
{
|
||||
"name": "Apple iPhone 5s",
|
||||
"width": 320,
|
||||
"height": 568,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 9_2_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13D15 Safari/601.1",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "ios",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Apple iPhone 6",
|
||||
"width": 375,
|
||||
"height": 667,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "ios"
|
||||
},
|
||||
{
|
||||
"name": "Apple iPhone 6 Plus",
|
||||
"width": 414,
|
||||
"height": 736,
|
||||
"pixelRatio": 3,
|
||||
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "ios",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Apple iPhone 6s",
|
||||
"width": 375,
|
||||
"height": 667,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "ios",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Apple iPhone 6s Plus",
|
||||
"width": 414,
|
||||
"height": 736,
|
||||
"pixelRatio": 3,
|
||||
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "ios"
|
||||
},
|
||||
{
|
||||
"name": "BlackBerry Z30",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "blackberryos"
|
||||
},
|
||||
{
|
||||
"name": "Geeksphone Keon",
|
||||
"width": 320,
|
||||
"height": 480,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Geeksphone Peak, Revolution",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 1.5,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Google Nexus S",
|
||||
"width": 320,
|
||||
"height": 533,
|
||||
"pixelRatio": 1.5,
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 2.3.4; en-us; Nexus S Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Google Nexus 4",
|
||||
"width": 384,
|
||||
"height": 640,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.4; en-us; Nexus 4 Build/JOP40D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2307.2 Mobile Safari/537.36",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "android",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Google Nexus 5",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 3,
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.4; en-us; Nexus 5 Build/JOP40D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2307.2 Mobile Safari/537.36",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "android",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Google Nexus 6",
|
||||
"width": 412,
|
||||
"height": 732,
|
||||
"pixelRatio": 3.5,
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.23 Mobile Safari/537.36",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "android",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Intex Cloud Fx",
|
||||
"width": 320,
|
||||
"height": 480,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; rv:32.0) Gecko/32.0 Firefox/32.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
},
|
||||
{
|
||||
"name": "KDDI Fx0",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; LGL25; rv:32.0) Gecko/32.0 Firefox/32.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
},
|
||||
{
|
||||
"name": "LG Fireweb",
|
||||
"width": 320,
|
||||
"height": 480,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; LG-D300; rv:18.1) Gecko/18.1 Firefox/18.1",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
},
|
||||
{
|
||||
"name": "LG Optimus L70",
|
||||
"width": 384,
|
||||
"height": 640,
|
||||
"pixelRatio": 1.25,
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.1599.103 Mobile Safari/537.36",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Nokia Lumia 520",
|
||||
"width": 320,
|
||||
"height": 533,
|
||||
"pixelRatio": 1.4,
|
||||
"userAgent": "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "android",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Nokia N9",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "OnePlus One",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 3,
|
||||
"userAgent": "Mozilla/5.0 (Android 5.1.1; Mobile; rv:43.0) Gecko/43.0 Firefox/43.0",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Samsung Galaxy S3",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Samsung Galaxy S4",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 3,
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; GT-I9505 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Samsung Galaxy S5",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 3,
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; GT-I9505 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "android",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Samsung Galaxy S6",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 4,
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; GT-I9505 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "android",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Sony Xperia Z3",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 3,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Spice Fire One Mi-FX1",
|
||||
"width": 320,
|
||||
"height": 480,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; rv:28.0) Gecko/28.0 Firefox/28.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
},
|
||||
{
|
||||
"name": "Symphony GoFox F15",
|
||||
"width": 320,
|
||||
"height": 480,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; rv:30.0) Gecko/30.0 Firefox/30.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
},
|
||||
{
|
||||
"name": "ZTE Open",
|
||||
"width": 320,
|
||||
"height": 480,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; ZTEOPEN; rv:18.1) Gecko/18.0 Firefox/18.1",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
},
|
||||
{
|
||||
"name": "ZTE Open II",
|
||||
"width": 320,
|
||||
"height": 480,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; OPEN2; rv:28.0) Gecko/28.0 Firefox/28.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
},
|
||||
{
|
||||
"name": "ZTE Open C",
|
||||
"width": 320,
|
||||
"height": 450,
|
||||
"pixelRatio": 1.5,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; OPENC; rv:32.0) Gecko/32.0 Firefox/32.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
},
|
||||
{
|
||||
"name": "Zen Fire 105",
|
||||
"width": 320,
|
||||
"height": 480,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; rv:28.0) Gecko/28.0 Firefox/28.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
}
|
||||
],
|
||||
"tablets": [
|
||||
{
|
||||
"name": "Amazon Kindle Fire HDX 8.9",
|
||||
"width": 1280,
|
||||
"height": 800,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "fireos",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Apple iPad",
|
||||
"width": 1024,
|
||||
"height": 768,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "ios"
|
||||
},
|
||||
{
|
||||
"name": "Apple iPad Air 2",
|
||||
"width": 1024,
|
||||
"height": 768,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (iPad; CPU OS 4_3_5 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8L1 Safari/6533.18.5",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "ios",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Apple iPad Mini",
|
||||
"width": 1024,
|
||||
"height": 768,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (iPad; CPU OS 4_3_5 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8L1 Safari/6533.18.5",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "ios"
|
||||
},
|
||||
{
|
||||
"name": "Apple iPad Mini 2",
|
||||
"width": 1024,
|
||||
"height": 768,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (iPad; CPU OS 4_3_5 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8L1 Safari/6533.18.5",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "ios",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "BlackBerry PlayBook",
|
||||
"width": 1024,
|
||||
"height": 600,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "blackberryos"
|
||||
},
|
||||
{
|
||||
"name": "Foxconn InFocus",
|
||||
"width": 1280,
|
||||
"height": 800,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Tablet; rv:32.0) Gecko/32.0 Firefox/32.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Google Nexus 7",
|
||||
"width": 960,
|
||||
"height": 600,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.3; Nexus 7 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2307.2 Mobile Safari/537.36",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "android",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Google Nexus 10",
|
||||
"width": 1280,
|
||||
"height": 800,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.3; Nexus 10 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2307.2 Mobile Safari/537.36",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Samsung Galaxy Note 2",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 2,
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Samsung Galaxy Note 3",
|
||||
"width": 360,
|
||||
"height": 640,
|
||||
"pixelRatio": 3,
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "android",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Tesla Model S",
|
||||
"width": 1200,
|
||||
"height": 1920,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (X11; Linux) AppleWebKit/534.34 (KHTML, like Gecko) QtCarBrowser Safari/534.34",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "linux"
|
||||
},
|
||||
{
|
||||
"name": "VIA Vixen",
|
||||
"width": 1024,
|
||||
"height": 600,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Tablet; rv:32.0) Gecko/32.0 Firefox/32.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
}
|
||||
],
|
||||
"laptops": [
|
||||
{
|
||||
"name": "Laptop (1366 x 768)",
|
||||
"width": 1366,
|
||||
"height": 768,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "",
|
||||
"touch": false,
|
||||
"firefoxOS": false,
|
||||
"os": "windows",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Laptop (1920 x 1080)",
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"pixelRatio": 1.5,
|
||||
"userAgent": "",
|
||||
"touch": false,
|
||||
"firefoxOS": false,
|
||||
"os": "windows",
|
||||
"featured": true
|
||||
},
|
||||
{
|
||||
"name": "Laptop (1920 x 1080) with touch",
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"pixelRatio": 1.5,
|
||||
"userAgent": "",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "windows"
|
||||
}
|
||||
],
|
||||
"televisions": [
|
||||
{
|
||||
"name": "720p HD Television",
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "",
|
||||
"touch": false,
|
||||
"firefoxOS": true,
|
||||
"os": "custom"
|
||||
},
|
||||
{
|
||||
"name": "1080p Full HD Television",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "",
|
||||
"touch": false,
|
||||
"firefoxOS": true,
|
||||
"os": "custom"
|
||||
},
|
||||
{
|
||||
"name": "4K Ultra HD Television",
|
||||
"width": 3840,
|
||||
"height": 2160,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "",
|
||||
"touch": false,
|
||||
"firefoxOS": true,
|
||||
"os": "custom"
|
||||
}
|
||||
],
|
||||
"consoles": [
|
||||
{
|
||||
"name": "Nintendo 3DS",
|
||||
"width": 320,
|
||||
"height": 240,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7585.EU",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "nintendo"
|
||||
},
|
||||
{
|
||||
"name": "Nintendo Wii U Gamepad",
|
||||
"width": 854,
|
||||
"height": 480,
|
||||
"pixelRatio": 0.87,
|
||||
"userAgent": "Mozilla/5.0 (Nintendo WiiU) AppleWebKit/536.28 (KHTML, like Gecko) NX/3.0.3.12.15 NintendoBrowser/4.1.1.9601.EU",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "nintendo"
|
||||
},
|
||||
{
|
||||
"name": "Sony PlayStation Vita",
|
||||
"width": 960,
|
||||
"height": 544,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Playstation Vita 1.61) AppleWebKit/531.22.8 (KHTML, like Gecko) Silk/3.2",
|
||||
"touch": true,
|
||||
"firefoxOS": false,
|
||||
"os": "playstation"
|
||||
}
|
||||
],
|
||||
"watches": [
|
||||
{
|
||||
"name": "LG G Watch",
|
||||
"width": 280,
|
||||
"height": 280,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "LG G Watch R",
|
||||
"width": 320,
|
||||
"height": 320,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Motorola Moto 360",
|
||||
"width": 320,
|
||||
"height": 290,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0.1; Moto 360 Build/LWX48T) AppleWebkit/537.36 (KHTML, like Gecko) Chrome/19.77.34.5 Mobile Safari/537.36",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "android"
|
||||
},
|
||||
{
|
||||
"name": "Samsung Gear Live",
|
||||
"width": 320,
|
||||
"height": 320,
|
||||
"pixelRatio": 1,
|
||||
"userAgent": "",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "android"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ SimpleTest.requestCompleteLog();
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
DevToolsUtils.testing = true;
|
||||
Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
|
||||
Services.prefs.setCharPref("devtools.devices.url",
|
||||
TEST_URI_ROOT + "devices.json");
|
||||
Services.prefs.setBoolPref("devtools.responsive.html.enabled", true);
|
||||
@ -34,8 +35,12 @@ registerCleanupFunction(() => {
|
||||
DevToolsUtils.testing = false;
|
||||
Services.prefs.clearUserPref("devtools.devices.url");
|
||||
Services.prefs.clearUserPref("devtools.responsive.html.enabled");
|
||||
Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
|
||||
});
|
||||
const { ResponsiveUIManager } = Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", {});
|
||||
const { loadDeviceList } = require("devtools/client/responsive.html/devices");
|
||||
|
||||
const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL";
|
||||
|
||||
/**
|
||||
* Open responsive design mode for the given tab.
|
||||
@ -125,3 +130,25 @@ var setViewportSize = Task.async(function* (ui, manager, width, height) {
|
||||
yield resized;
|
||||
}
|
||||
});
|
||||
|
||||
function openDeviceModal(ui) {
|
||||
let { document } = ui.toolWindow;
|
||||
let select = document.querySelector(".viewport-device-selector");
|
||||
let modal = document.querySelector(".device-modal");
|
||||
let editDeviceOption = [...select.options].filter(o => {
|
||||
return o.value === OPEN_DEVICE_MODAL_VALUE;
|
||||
})[0];
|
||||
|
||||
info("Checking initial device modal state");
|
||||
ok(modal.classList.contains("hidden"),
|
||||
"The device modal is hidden by default.");
|
||||
|
||||
info("Opening device modal through device selector.");
|
||||
EventUtils.synthesizeMouseAtCenter(select, {type: "mousedown"},
|
||||
ui.toolWindow);
|
||||
EventUtils.synthesizeMouseAtCenter(editDeviceOption, {type: "mouseup"},
|
||||
ui.toolWindow);
|
||||
|
||||
ok(!modal.classList.contains("hidden"),
|
||||
"The device modal is displayed.");
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test updating the device `displayed` property
|
||||
|
||||
const {
|
||||
addDevice,
|
||||
addDeviceType,
|
||||
updateDeviceDisplayed,
|
||||
} = require("devtools/client/responsive.html/actions/devices");
|
||||
|
||||
add_task(function* () {
|
||||
let store = Store();
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
let device = {
|
||||
"name": "Firefox OS Flame",
|
||||
"width": 320,
|
||||
"height": 570,
|
||||
"pixelRatio": 1.5,
|
||||
"userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
|
||||
"touch": true,
|
||||
"firefoxOS": true,
|
||||
"os": "fxos"
|
||||
};
|
||||
|
||||
dispatch(addDeviceType("phones"));
|
||||
dispatch(addDevice(device, "phones"));
|
||||
dispatch(updateDeviceDisplayed(device, "phones", true));
|
||||
|
||||
equal(getState().devices.phones.length, 1,
|
||||
"Correct number of phones");
|
||||
ok(getState().devices.phones[0].displayed,
|
||||
"Device phone list contains enabled Firefox OS Flame");
|
||||
});
|
@ -11,3 +11,4 @@ firefox-appdir = browser
|
||||
[test_change_viewport_device.js]
|
||||
[test_resize_viewport.js]
|
||||
[test_rotate_viewport.js]
|
||||
[test_update_device_displayed.js]
|
||||
|
@ -32,9 +32,12 @@ const device = {
|
||||
// Whether or not it is a touch device
|
||||
touch: PropTypes.bool,
|
||||
|
||||
// The operating system of the device
|
||||
// The operating system of the device
|
||||
os: PropTypes.String,
|
||||
|
||||
// Whether or not the device is displayed in the device selector
|
||||
displayed: PropTypes.bool,
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@ -63,6 +66,9 @@ exports.devices = {
|
||||
// An array of watch devices
|
||||
watches: PropTypes.arrayOf(PropTypes.shape(device)),
|
||||
|
||||
// Whether or not the device modal is open
|
||||
isModalOpen: PropTypes.bool,
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -4,20 +4,100 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const promise = require("promise");
|
||||
const { defer } = require("promise");
|
||||
|
||||
module.exports = {
|
||||
// The prefix used for RDM messages in content.
|
||||
// see: devtools/client/responsivedesign/responsivedesign-child.js
|
||||
const MESSAGE_PREFIX = "ResponsiveMode:";
|
||||
const REQUEST_DONE_SUFFIX = ":Done";
|
||||
|
||||
waitForMessage(mm, message) {
|
||||
let deferred = promise.defer();
|
||||
/**
|
||||
* Registers a message `listener` that is called every time messages of
|
||||
* specified `message` is emitted on the given message manager.
|
||||
* @param {nsIMessageListenerManager} mm
|
||||
* The Message Manager
|
||||
* @param {String} message
|
||||
* The message. It will be prefixed with the constant `MESSAGE_PREFIX`
|
||||
* @param {Function} listener
|
||||
* The listener function that processes the message.
|
||||
*/
|
||||
function on(mm, message, listener) {
|
||||
mm.addMessageListener(MESSAGE_PREFIX + message, listener);
|
||||
}
|
||||
exports.on = on;
|
||||
|
||||
let onMessage = event => {
|
||||
mm.removeMessageListener(message, onMessage);
|
||||
deferred.resolve();
|
||||
};
|
||||
mm.addMessageListener(message, onMessage);
|
||||
/**
|
||||
* Removes a message `listener` for the specified `message` on the given
|
||||
* message manager.
|
||||
* @param {nsIMessageListenerManager} mm
|
||||
* The Message Manager
|
||||
* @param {String} message
|
||||
* The message. It will be prefixed with the constant `MESSAGE_PREFIX`
|
||||
* @param {Function} listener
|
||||
* The listener function that processes the message.
|
||||
*/
|
||||
function off(mm, message, listener) {
|
||||
mm.removeMessageListener(MESSAGE_PREFIX + message, listener);
|
||||
}
|
||||
exports.off = off;
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
/**
|
||||
* Resolves a promise the next time the specified `message` is sent over the
|
||||
* given message manager.
|
||||
* @param {nsIMessageListenerManager} mm
|
||||
* The Message Manager
|
||||
* @param {String} message
|
||||
* The message. It will be prefixed with the constant `MESSAGE_PREFIX`
|
||||
* @returns {Promise}
|
||||
* A promise that is resolved when the given message is emitted.
|
||||
*/
|
||||
function once(mm, message) {
|
||||
let { resolve, promise } = defer();
|
||||
|
||||
};
|
||||
on(mm, message, function onMessage({data}) {
|
||||
off(mm, message, onMessage);
|
||||
resolve(data);
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
exports.once = once;
|
||||
|
||||
/**
|
||||
* Asynchronously emit a `message` to the listeners of the given message
|
||||
* manager.
|
||||
*
|
||||
* @param {nsIMessageListenerManager} mm
|
||||
* The Message Manager
|
||||
* @param {String} message
|
||||
* The message. It will be prefixed with the constant `MESSAGE_PREFIX`.
|
||||
* @param {Object} data
|
||||
* A JSON object containing data to be delivered to the listeners.
|
||||
*/
|
||||
function emit(mm, message, data) {
|
||||
mm.sendAsyncMessage(MESSAGE_PREFIX + message, data);
|
||||
}
|
||||
exports.emit = emit;
|
||||
|
||||
/**
|
||||
* Asynchronously send a "request" over the given message manager, and returns
|
||||
* a promise that is resolved when the request is complete.
|
||||
*
|
||||
* @param {nsIMessageListenerManager} mm
|
||||
* The Message Manager
|
||||
* @param {String} message
|
||||
* The message. It will be prefixed with the constant `MESSAGE_PREFIX`, and
|
||||
* also suffixed with `REQUEST_DONE_SUFFIX` for the reply.
|
||||
* @param {Object} data
|
||||
* A JSON object containing data to be delivered to the listeners.
|
||||
* @returns {Promise}
|
||||
* A promise that is resolved when the request is done.
|
||||
*/
|
||||
function request(mm, message, data) {
|
||||
let done = once(mm, message + REQUEST_DONE_SUFFIX);
|
||||
|
||||
emit(mm, message, data);
|
||||
|
||||
return done;
|
||||
}
|
||||
exports.request = request;
|
||||
|
@ -150,12 +150,14 @@ var global = this;
|
||||
|
||||
function screenshot() {
|
||||
let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
||||
let width = content.innerWidth;
|
||||
let height = content.innerHeight;
|
||||
let ratio = content.devicePixelRatio;
|
||||
let width = content.innerWidth * ratio;
|
||||
let height = content.innerHeight * ratio;
|
||||
canvas.mozOpaque = true;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
let ctx = canvas.getContext("2d");
|
||||
ctx.scale(ratio, ratio);
|
||||
ctx.drawWindow(content, content.scrollX, content.scrollY, width, height, "#fff");
|
||||
sendAsyncMessage("ResponsiveMode:RequestScreenshot:Done", canvas.toDataURL());
|
||||
}
|
||||
|
@ -3,13 +3,38 @@
|
||||
* 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/. */
|
||||
|
||||
.theme-dark,
|
||||
.theme-light {
|
||||
--number-color: var(--theme-highlight-green);
|
||||
--string-color: var(--theme-highlight-orange);
|
||||
--null-color: var(--theme-comment);
|
||||
--object-color: var(--theme-body-color);
|
||||
--caption-color: var(--theme-highlight-blue);
|
||||
--location-color: var(--theme-content-color1);
|
||||
--source-link-color: var(--theme-highlight-blue);
|
||||
--node-color: var(--theme-highlight-bluegrey);
|
||||
--reference-color: var(--theme-highlight-purple);
|
||||
}
|
||||
|
||||
.theme-firebug {
|
||||
--number-color: #000088;
|
||||
--string-color: #FF0000;
|
||||
--null-color: #787878;
|
||||
--object-color: DarkGreen;
|
||||
--caption-color: #444444;
|
||||
--location-color: #555555;
|
||||
--source-link-color: blue;
|
||||
--node-color: rgb(0, 0, 136);
|
||||
--reference-color: rgb(102, 102, 255);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
.objectLink:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
.inline {
|
||||
display: inline;
|
||||
white-space: normal;
|
||||
@ -17,7 +42,7 @@
|
||||
|
||||
.objectBox-object {
|
||||
font-weight: bold;
|
||||
color: DarkGreen;
|
||||
color: var(--object-color);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@ -33,22 +58,22 @@
|
||||
.objectLink-element,
|
||||
.objectLink-textNode,
|
||||
.objectBox-array > .length {
|
||||
color: #000088;
|
||||
color: var(--number-color);
|
||||
}
|
||||
|
||||
.objectBox-string {
|
||||
color: #FF0000;
|
||||
color: var(--string-color);
|
||||
}
|
||||
|
||||
.objectLink-function,
|
||||
.objectBox-stackTrace,
|
||||
.objectLink-profile {
|
||||
color: DarkGreen;
|
||||
color: var(--object-color);
|
||||
}
|
||||
|
||||
.objectLink-Location {
|
||||
font-style: italic;
|
||||
color: #555555;
|
||||
color: var(--location-color);
|
||||
}
|
||||
|
||||
.objectBox-null,
|
||||
@ -56,14 +81,7 @@
|
||||
.objectBox-hint,
|
||||
.logRowHint {
|
||||
font-style: italic;
|
||||
color: #787878;
|
||||
}
|
||||
|
||||
.objectBox-scope {
|
||||
color: #707070;
|
||||
}
|
||||
.objectBox-optimizedAway {
|
||||
color: #909090;
|
||||
color: var(--null-color);
|
||||
}
|
||||
|
||||
.objectLink-sourceLink {
|
||||
@ -72,12 +90,7 @@
|
||||
top: 2px;
|
||||
padding-left: 8px;
|
||||
font-weight: bold;
|
||||
color: #0000FF;
|
||||
}
|
||||
|
||||
.objectLink-sourceLink > .systemLink {
|
||||
float: right;
|
||||
color: #FF0000;
|
||||
color: var(--source-link-color);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
@ -88,7 +101,7 @@
|
||||
.objectLink-object,
|
||||
.objectLink-Date {
|
||||
font-weight: bold;
|
||||
color: DarkGreen;
|
||||
color: var(--object-color);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@ -101,7 +114,7 @@
|
||||
.objectLink-NamedNodeMap .arrayRightBracket,
|
||||
.objectLink-Attr .attrEqual,
|
||||
.objectLink-Attr .attrTitle {
|
||||
color: rgb(0, 0, 136)
|
||||
color: var(--node-color);
|
||||
}
|
||||
|
||||
.objectLink-object .nodeName {
|
||||
@ -133,63 +146,36 @@
|
||||
|
||||
.objectLink-Reference {
|
||||
font-weight: bold;
|
||||
color: rgb(102, 102, 255);
|
||||
color: var(--reference-color);
|
||||
}
|
||||
|
||||
.objectBox-array > .objectTitle {
|
||||
font-weight: bold;
|
||||
color: DarkGreen;
|
||||
color: var(--object-color);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
.caption {
|
||||
font-weight: bold;
|
||||
color: #444444;
|
||||
color: var(--caption-color);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Light Theme & Dark Theme */
|
||||
|
||||
.theme-dark .domLabel,
|
||||
.theme-light .domLabel {
|
||||
color: var(--theme-highlight-blue);
|
||||
}
|
||||
|
||||
.theme-dark .objectBox-array .length,
|
||||
.theme-light .objectBox-array .length,
|
||||
.theme-dark .objectBox-number,
|
||||
.theme-light .objectBox-number {
|
||||
color: var(--theme-highlight-green);
|
||||
}
|
||||
|
||||
.theme-dark .objectBox-string,
|
||||
.theme-light .objectBox-string {
|
||||
color: var(--theme-highlight-orange);
|
||||
}
|
||||
/* Themes */
|
||||
|
||||
.theme-dark .objectBox-null,
|
||||
.theme-dark .objectBox-undefined,
|
||||
.theme-light .objectBox-null,
|
||||
.theme-light .objectBox-undefined {
|
||||
font-style: normal;
|
||||
color: var(--theme-comment);
|
||||
}
|
||||
|
||||
.theme-dark .objectBox-array,
|
||||
.theme-light .objectBox-array {
|
||||
color: var(--theme-body-color);
|
||||
}
|
||||
|
||||
.theme-dark .objectBox-object,
|
||||
.theme-light .objectBox-object {
|
||||
font-weight: normal;
|
||||
color: var(--theme-highlight-blue);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.theme-dark .caption,
|
||||
.theme-light .caption {
|
||||
font-weight: normal;
|
||||
color: var(--theme-highlight-blue);
|
||||
}
|
||||
|
@ -46,10 +46,6 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.treeTable .treeRow:hover {
|
||||
background-color: var(--theme-body-background);
|
||||
}
|
||||
|
||||
/* Filtering */
|
||||
.treeTable .treeRow.hidden {
|
||||
display: none;
|
||||
@ -82,17 +78,6 @@
|
||||
background-size: 9px 9px !important;
|
||||
}
|
||||
|
||||
/* Default toggle icon. The immediate children operator must be
|
||||
used here since there might be nested tree components inside
|
||||
a tree and we don't want to alter those. */
|
||||
.treeTable .treeRow.hasChildren > .treeLabelCell > .treeIcon {
|
||||
background-image: url(chrome://devtools/skin/images/firebug/twisty-closed-firebug.svg);
|
||||
}
|
||||
|
||||
.treeTable .treeRow.hasChildren.opened > .treeLabelCell > .treeIcon {
|
||||
background-image: url(chrome://devtools/skin/images/firebug/twisty-open-firebug.svg);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Header */
|
||||
|
||||
@ -149,40 +134,47 @@
|
||||
|
||||
/* Light Theme: toggle icon */
|
||||
.theme-light .treeTable .treeRow.hasChildren > .treeLabelCell > .treeIcon {
|
||||
background-image: url("chrome://devtools/skin/images/controls.png");
|
||||
background-image: url(chrome://devtools/skin/images/controls.png);
|
||||
background-size: 56px 28px;
|
||||
background-position: 0 -14px;
|
||||
}
|
||||
|
||||
.theme-light .treeTable .treeRow.hasChildren.opened > .treeLabelCell > .treeIcon {
|
||||
background-image: url("chrome://devtools/skin/images/controls.png");
|
||||
background-image: url(chrome://devtools/skin/images/controls.png);
|
||||
background-size: 56px 28px;
|
||||
background-position: -14px -14px;
|
||||
}
|
||||
|
||||
/* Dark Theme: toggle icon */
|
||||
.theme-dark .treeTable .treeRow.hasChildren > .treeLabelCell > .treeIcon {
|
||||
background-image: url("chrome://devtools/skin/images/controls.png");
|
||||
background-image: url(chrome://devtools/skin/images/controls.png);
|
||||
background-size: 56px 28px;
|
||||
background-position: -28px -14px;
|
||||
}
|
||||
|
||||
.theme-dark .treeTable .treeRow.hasChildren.opened > .treeLabelCell > .treeIcon {
|
||||
background-image: url("chrome://devtools/skin/images/controls.png");
|
||||
background-image: url(chrome://devtools/skin/images/controls.png);
|
||||
background-size: 56px 28px;
|
||||
background-position: -42px -14px;
|
||||
}
|
||||
|
||||
.theme-dark .treeTable .treeRow:hover {
|
||||
background-color: var(--theme-selection-background-semitransparent);
|
||||
/* Dark and Light Themes: Support for retina displays */
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
.theme-dark .treeTable .treeRow.hasChildren > .treeLabelCell > .treeIcon,
|
||||
.theme-light .treeTable .treeRow.hasChildren > .treeLabelCell > .treeIcon {
|
||||
background-image: url("chrome://devtools/skin/images/controls@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark and Light Themes: colors */
|
||||
.theme-light .treeTable .treeRow:hover,
|
||||
.theme-dark .treeTable .treeRow:hover {
|
||||
background-color: var(--theme-selection-background) !important;
|
||||
}
|
||||
|
||||
.theme-firebug .treeTable .treeRow:hover {
|
||||
background-color: var(--theme-body-background);
|
||||
}
|
||||
|
||||
.theme-light .treeTable .treeLabel,
|
||||
.theme-dark .treeTable .treeLabel {
|
||||
color: var(--theme-highlight-pink);
|
||||
@ -192,10 +184,6 @@
|
||||
color: var(--theme-highlight-pink);
|
||||
}
|
||||
|
||||
/* Dark and Light Themes: Support for retina displays */
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
.theme-dark .treeTable .treeRow.hasChildren > .treeLabelCell > .treeIcon,
|
||||
.theme-light .treeTable .treeRow.hasChildren > .treeLabelCell > .treeIcon {
|
||||
background-image: url("chrome://devtools/skin/images/controls@2x.png");
|
||||
}
|
||||
.theme-firebug .treeTable .treeLabel {
|
||||
color: var(--theme-body-color);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ code, and optionally help with indentation.
|
||||
|
||||
# Upgrade
|
||||
|
||||
Currently used version is 5.13.0. To upgrade, download a new version of
|
||||
Currently used version is 5.13.2. To upgrade, download a new version of
|
||||
CodeMirror from the project's page [1] and replace all JavaScript and
|
||||
CSS files inside the codemirror directory [2].
|
||||
|
||||
|
0
devtools/client/sourceeditor/codemirror/addon/comment/comment.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/comment/comment.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/comment/continuecomment.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/comment/continuecomment.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/dialog/dialog.css
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/dialog/dialog.css
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/dialog/dialog.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/dialog/dialog.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/edit/closebrackets.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/edit/closebrackets.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/edit/closetag.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/edit/closetag.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/edit/continuelist.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/edit/continuelist.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/edit/matchbrackets.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/edit/matchbrackets.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/edit/matchtags.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/edit/matchtags.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/edit/trailingspace.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/edit/trailingspace.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/fold/brace-fold.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/fold/brace-fold.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/fold/comment-fold.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/fold/comment-fold.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/fold/foldcode.js
vendored
Executable file → Normal file
0
devtools/client/sourceeditor/codemirror/addon/fold/foldcode.js
vendored
Executable file → Normal file
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user