mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-16 14:55:47 +00:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
4a4aa387d6
@ -76,7 +76,6 @@ browser/locales/**
|
||||
browser/extensions/loop/**
|
||||
|
||||
# devtools/ exclusions
|
||||
devtools/*.js
|
||||
devtools/client/canvasdebugger/**
|
||||
devtools/client/commandline/**
|
||||
devtools/client/debugger/**
|
||||
|
@ -129,11 +129,11 @@
|
||||
#endif
|
||||
</description>
|
||||
<description class="text-blurb" id="communityExperimentalDesc">
|
||||
&community.exp.start;<label class="text-link" href="http://www.mozilla.org/">&community.exp.mozillaLink;</label>&community.exp.middle;<label class="text-link" href="about:credits">&community.exp.creditsLink;</label>&community.exp.end;
|
||||
&community.exp.start;<label class="text-link" href="http://www.mozilla.org/">&community.exp.mozillaLink;</label>&community.exp.middle;<label class="text-link" useoriginprincipal="true" href="about:credits">&community.exp.creditsLink;</label>&community.exp.end;
|
||||
</description>
|
||||
</vbox>
|
||||
<description class="text-blurb" id="communityDesc">
|
||||
&community.start2;<label class="text-link" href="http://www.mozilla.org/">&community.mozillaLink;</label>&community.middle2;<label class="text-link" href="about:credits">&community.creditsLink;</label>&community.end3;
|
||||
&community.start2;<label class="text-link" href="http://www.mozilla.org/">&community.mozillaLink;</label>&community.middle2;<label class="text-link" useoriginprincipal="true" href="about:credits">&community.creditsLink;</label>&community.end3;
|
||||
</description>
|
||||
<description class="text-blurb" id="contributeDesc">
|
||||
&helpus.start;<label class="text-link" href="https://sendto.mozilla.org/page/contribute/Give-Now?source=mozillaorg_default_footer&ref=firefox_about&utm_campaign=firefox_about&tm_source=firefox&tm_medium=referral&utm_content=20140929_FireFoxAbout">&helpus.donateLink;</label>&helpus.middle;<label class="text-link" href="http://www.mozilla.org/contribute/">&helpus.getInvolvedLink;</label>&helpus.end;
|
||||
@ -143,8 +143,8 @@
|
||||
</hbox>
|
||||
<vbox id="bottomBox">
|
||||
<hbox pack="center">
|
||||
<label class="text-link bottom-link" href="about:license">&bottomLinks.license;</label>
|
||||
<label class="text-link bottom-link" href="about:rights">&bottomLinks.rights;</label>
|
||||
<label class="text-link bottom-link" useoriginprincipal="true" href="about:license">&bottomLinks.license;</label>
|
||||
<label class="text-link bottom-link" useoriginprincipal="true" href="about:rights">&bottomLinks.rights;</label>
|
||||
<label class="text-link bottom-link" href="https://www.mozilla.org/privacy/">&bottomLinks.privacy;</label>
|
||||
</hbox>
|
||||
<description id="trademark">&trademarkInfo.part1;</description>
|
||||
|
@ -217,15 +217,15 @@
|
||||
|
||||
// if it's an unknown error or there's no title or description
|
||||
// defined, get the generic message
|
||||
var errTitle = document.getElementById("et_" + err).innerHTML;
|
||||
var errDesc = document.getElementById("ed_" + err).innerHTML;
|
||||
var errTitle = document.getElementById("et_" + err);
|
||||
var errDesc = document.getElementById("ed_" + err);
|
||||
if (!errTitle || !errDesc)
|
||||
{
|
||||
errTitle = document.getElementById("et_generic").innerHTML;
|
||||
errDesc = document.getElementById("ed_generic").innerHTML;
|
||||
errTitle = document.getElementById("et_generic");
|
||||
errDesc = document.getElementById("ed_generic");
|
||||
}
|
||||
|
||||
document.querySelector(".title-text").innerHTML = errTitle;
|
||||
document.querySelector(".title-text").innerHTML = errTitle.innerHTML;
|
||||
|
||||
var sd = document.getElementById("errorShortDescText");
|
||||
if (sd) {
|
||||
@ -244,7 +244,7 @@
|
||||
var ld = document.getElementById("errorLongDesc");
|
||||
if (ld)
|
||||
{
|
||||
ld.innerHTML = errDesc;
|
||||
ld.innerHTML = errDesc.innerHTML;
|
||||
}
|
||||
|
||||
if (err == "sslv3Used") {
|
||||
@ -600,19 +600,21 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="weakCryptoAdvancedPanel" class="advanced-panel">
|
||||
<div id="weakCryptoAdvancedDescription">
|
||||
<p>&weakCryptoAdvanced.longDesc;</p>
|
||||
<div id="advancedPanelContainer">
|
||||
<div id="weakCryptoAdvancedPanel" class="advanced-panel">
|
||||
<div id="weakCryptoAdvancedDescription">
|
||||
<p>&weakCryptoAdvanced.longDesc;</p>
|
||||
</div>
|
||||
<div id="advancedLongDesc" />
|
||||
<div id="overrideWeakCryptoPanel">
|
||||
<a id="overrideWeakCrypto" href="#">&weakCryptoAdvanced.override;</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="advancedLongDesc" />
|
||||
<div id="overrideWeakCryptoPanel">
|
||||
<a id="overrideWeakCrypto" href="#">&weakCryptoAdvanced.override;</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="badCertAdvancedPanel" class="advanced-panel">
|
||||
<p id="badCertTechnicalInfo"/>
|
||||
<button id="exceptionDialogButton">&securityOverride.exceptionButtonLabel;</button>
|
||||
<div id="badCertAdvancedPanel" class="advanced-panel">
|
||||
<p id="badCertTechnicalInfo"/>
|
||||
<button id="exceptionDialogButton">&securityOverride.exceptionButtonLabel;</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -83,6 +83,18 @@ var EventListener = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (content.document.documentURI.startsWith("about:reader")) {
|
||||
if (event.type == "load" &&
|
||||
!content.document.body.classList.contains("loaded")) {
|
||||
// Don't restore the scroll position of an about:reader page at this
|
||||
// point; listen for the custom event dispatched from AboutReader.jsm.
|
||||
content.addEventListener("AboutReaderContentReady", this);
|
||||
return;
|
||||
}
|
||||
|
||||
content.removeEventListener("AboutReaderContentReady", this);
|
||||
}
|
||||
|
||||
// Restore the form data and scroll position. If we're not currently
|
||||
// restoring a tab state then this call will simply be a noop.
|
||||
gContentRestore.restoreDocument();
|
||||
|
@ -32,6 +32,7 @@ support-files =
|
||||
browser_sessionHistory_slow.sjs
|
||||
browser_scrollPositions_sample.html
|
||||
browser_scrollPositions_sample_frameset.html
|
||||
browser_scrollPositions_readerModeArticle.html
|
||||
browser_sessionStorage.html
|
||||
browser_248970_b_sample.html
|
||||
browser_339445_sample.html
|
||||
@ -56,7 +57,7 @@ support-files =
|
||||
|
||||
#NB: the following are disabled
|
||||
# browser_464620_a.html
|
||||
# browser_464620_b.html
|
||||
# browser_464620_b.html
|
||||
# browser_464620_xd.html
|
||||
|
||||
|
||||
@ -106,6 +107,7 @@ skip-if = e10s # Bug 1271024
|
||||
[browser_replace_load.js]
|
||||
[browser_restore_redirect.js]
|
||||
[browser_scrollPositions.js]
|
||||
[browser_scrollPositionsReaderMode.js]
|
||||
[browser_sessionHistory.js]
|
||||
[browser_sessionStorage.js]
|
||||
[browser_swapDocShells.js]
|
||||
|
@ -151,11 +151,3 @@ add_task(function test_scroll_background_tabs() {
|
||||
|
||||
yield BrowserTestUtils.closeWindow(newWin);
|
||||
});
|
||||
|
||||
function* checkScroll(tab, expected, msg) {
|
||||
let browser = tab.linkedBrowser;
|
||||
yield TabStateFlusher.flush(browser);
|
||||
|
||||
let scroll = JSON.parse(ss.getTabState(tab)).scroll || null;
|
||||
is(JSON.stringify(scroll), JSON.stringify(expected), msg);
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const BASE = "http://example.com/browser/browser/components/sessionstore/test/"
|
||||
const READER_MODE_URL = "about:reader?url=" +
|
||||
encodeURIComponent(BASE + "browser_scrollPositions_readerModeArticle.html");
|
||||
|
||||
// Randomized set of scroll positions we will use in this test.
|
||||
const SCROLL_READER_MODE_Y = Math.round(400 * (1 + Math.random()));
|
||||
const SCROLL_READER_MODE_STR = "0," + SCROLL_READER_MODE_Y;
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
/**
|
||||
* Test that scroll positions of about reader page after restoring background
|
||||
* tabs in a restored window (bug 1153393).
|
||||
*/
|
||||
add_task(function test_scroll_background_about_reader_tabs() {
|
||||
pushPrefs(["browser.sessionstore.restore_on_demand", true]);
|
||||
|
||||
let newWin = yield BrowserTestUtils.openNewBrowserWindow();
|
||||
let tab = newWin.gBrowser.addTab(READER_MODE_URL);
|
||||
let browser = tab.linkedBrowser;
|
||||
yield Promise.all([
|
||||
BrowserTestUtils.browserLoaded(browser),
|
||||
BrowserTestUtils.waitForContentEvent(browser, "AboutReaderContentReady")
|
||||
]);
|
||||
|
||||
// Scroll down a little.
|
||||
yield sendMessage(browser, "ss-test:setScrollPosition", {x: 0, y: SCROLL_READER_MODE_Y});
|
||||
yield checkScroll(tab, {scroll: SCROLL_READER_MODE_STR}, "scroll is fine");
|
||||
|
||||
// Close the window
|
||||
yield BrowserTestUtils.closeWindow(newWin);
|
||||
|
||||
// Now restore the window
|
||||
newWin = ss.undoCloseWindow(0);
|
||||
|
||||
// Make sure to wait for the window to be restored.
|
||||
yield BrowserTestUtils.waitForEvent(newWin, "SSWindowStateReady");
|
||||
|
||||
is(newWin.gBrowser.tabs.length, 2, "There should be two tabs");
|
||||
|
||||
// The second tab should be the one we loaded URL at still
|
||||
tab = newWin.gBrowser.tabs[1];
|
||||
yield promiseTabRestoring(tab);
|
||||
|
||||
ok(tab.hasAttribute("pending"), "Tab should be pending");
|
||||
browser = tab.linkedBrowser;
|
||||
|
||||
// Ensure there are no pending queued messages in the child.
|
||||
yield TabStateFlusher.flush(browser);
|
||||
|
||||
// Now check to see if the background tab remembers where it
|
||||
// should be scrolled to.
|
||||
newWin.gBrowser.selectedTab = tab;
|
||||
yield Promise.all([
|
||||
promiseTabRestored(tab),
|
||||
BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "AboutReaderContentReady")
|
||||
]);
|
||||
|
||||
yield checkScroll(tab, {scroll: SCROLL_READER_MODE_STR}, "scroll is still fine");
|
||||
|
||||
yield BrowserTestUtils.closeWindow(newWin);
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Article title</title>
|
||||
<meta name="description" content="This is the article description." />
|
||||
</head>
|
||||
<body>
|
||||
<header>Site header</header>
|
||||
<div>
|
||||
<h1>Article title</h1>
|
||||
<h2 class="author">by Jane Doe</h2>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.</p>
|
||||
<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
|
||||
<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
|
||||
<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
|
||||
<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
|
||||
<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
|
||||
<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
|
||||
<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
|
||||
<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
|
||||
<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
|
||||
<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
|
||||
<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -553,3 +553,10 @@ function popPrefs() {
|
||||
});
|
||||
}
|
||||
|
||||
function* checkScroll(tab, expected, msg) {
|
||||
let browser = tab.linkedBrowser;
|
||||
yield TabStateFlusher.flush(browser);
|
||||
|
||||
let scroll = JSON.parse(ss.getTabState(tab)).scroll || null;
|
||||
is(JSON.stringify(scroll), JSON.stringify(expected), msg);
|
||||
}
|
||||
|
@ -819,7 +819,7 @@ menuitem:not([type]):not(.menuitem-tooltip):not(.menuitem-iconic-tooltip) {
|
||||
--urlbar-border-color: ThreeDShadow;
|
||||
}
|
||||
|
||||
#main-window:-moz-lwtheme {
|
||||
#navigator-toolbox:-moz-lwtheme {
|
||||
--urlbar-border-color: rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
|
@ -73,4 +73,17 @@ menu.subviewbutton > .menu-right > image {
|
||||
|
||||
toolbarpaletteitem[place="palette"] > .toolbarbutton-1 > .toolbarbutton-menubutton-button {
|
||||
padding: 3px 1px;
|
||||
}
|
||||
}
|
||||
|
||||
#PanelUI-remotetabs-tabslist > label[itemtype="client"] {
|
||||
-moz-padding-start: 6px;
|
||||
}
|
||||
|
||||
.PanelUI-remotetabs-notabsforclient-label {
|
||||
margin-left: 19px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#PanelUI-remotetabs-tabslist {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ html {
|
||||
}
|
||||
|
||||
.item.client .item-twisty-container {
|
||||
width: 16px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
|
||||
}
|
||||
|
@ -68,6 +68,15 @@ body.certerror #advancedButton {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#advancedPanelContainer {
|
||||
position: absolute;
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.advanced-panel {
|
||||
/* Hidden until the link is clicked */
|
||||
display: none;
|
||||
@ -78,7 +87,6 @@ body.certerror #advancedButton {
|
||||
padding: 0 12px 12px 12px;
|
||||
box-shadow: 0 0 4px #ddd;
|
||||
font-size: 0.9em;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
#overrideWeakCryptoPanel {
|
||||
|
@ -1101,7 +1101,7 @@ toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
|
||||
--urlbar-border-color-hover: var(--urlbar-border-color);
|
||||
}
|
||||
|
||||
#main-window:-moz-lwtheme {
|
||||
#navigator-toolbox:-moz-lwtheme {
|
||||
--urlbar-border-color: var(--toolbarbutton-hover-bordercolor);
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ html {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
padding-top: 5px;
|
||||
width: 9px; /* The image's width is 9 pixels */
|
||||
min-width: 9px; /* The image's width is 9 pixels */
|
||||
height: 9px;
|
||||
}
|
||||
|
||||
|
@ -5,29 +5,18 @@
|
||||
"globals": {
|
||||
"atob": true,
|
||||
"btoa": true,
|
||||
"Cc": true,
|
||||
"Ci": true,
|
||||
"Components": true,
|
||||
"clearInterval": true,
|
||||
"clearTimeout": true,
|
||||
"console": true,
|
||||
"Cr": true,
|
||||
"Cu": true,
|
||||
"devtools": true,
|
||||
"dump": true,
|
||||
"EventEmitter": true,
|
||||
"exports": true,
|
||||
"isWorker": true,
|
||||
"loader": true,
|
||||
"module": true,
|
||||
"reportError": true,
|
||||
"require": true,
|
||||
"setInterval": true,
|
||||
"setTimeout": true,
|
||||
"Services": true,
|
||||
"Task": true,
|
||||
"XPCNativeWrapper": true,
|
||||
"XPCOMUtils": true,
|
||||
"_Iterator": true,
|
||||
},
|
||||
"rules": {
|
||||
|
21
devtools/bootstrap.js
vendored
21
devtools/bootstrap.js
vendored
@ -2,6 +2,9 @@
|
||||
* 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/. */
|
||||
|
||||
/* global content */
|
||||
/* exported startup, shutdown, install, uninstall */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
@ -10,7 +13,7 @@ const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
function actionOccurred(id) {
|
||||
let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
let Telemetry = require("devtools/client/shared/telemetry");;
|
||||
let Telemetry = require("devtools/client/shared/telemetry");
|
||||
let telemetry = new Telemetry();
|
||||
telemetry.actionOccurred(id);
|
||||
}
|
||||
@ -59,7 +62,7 @@ function MultiWindowKeyListener({ keyCode, ctrlKey, altKey, callback }) {
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
let getTopLevelWindow = function (window) {
|
||||
return window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
@ -73,7 +76,7 @@ let getTopLevelWindow = function (window) {
|
||||
function reload(event) {
|
||||
// We automatically reload the toolbox if we are on a browser tab
|
||||
// with a toolbox already opened
|
||||
let top = getTopLevelWindow(event.view)
|
||||
let top = getTopLevelWindow(event.view);
|
||||
let isBrowser = top.location.href.includes("/browser.xul");
|
||||
let reloadToolbox = false;
|
||||
if (isBrowser && top.gBrowser) {
|
||||
@ -95,12 +98,13 @@ function reload(event) {
|
||||
reopenBrowserConsole = true;
|
||||
}
|
||||
|
||||
dump("Reload DevTools. (reload-toolbox:"+reloadToolbox+")\n");
|
||||
dump("Reload DevTools. (reload-toolbox:" + reloadToolbox + ")\n");
|
||||
|
||||
// Invalidate xul cache in order to see changes made to chrome:// files
|
||||
Services.obs.notifyObservers(null, "startupcache-invalidate", null);
|
||||
|
||||
// This frame script is going to be executed in all processes: parent and childs
|
||||
// This frame script is going to be executed in all processes:
|
||||
// parent and child
|
||||
Services.ppmm.loadProcessScript("data:,new " + function () {
|
||||
/* Flush message manager cached frame scripts as well as chrome locales */
|
||||
let obs = Components.classes["@mozilla.org/observer-service;1"]
|
||||
@ -141,7 +145,8 @@ function reload(event) {
|
||||
}
|
||||
// We have to use a frame script to query "baseURI"
|
||||
mm.loadFrameScript("data:text/javascript,new " + function () {
|
||||
let isJSONView = content.document.baseURI.startsWith("resource://devtools/");
|
||||
let isJSONView =
|
||||
content.document.baseURI.startsWith("resource://devtools/");
|
||||
if (isJSONView) {
|
||||
content.location.reload();
|
||||
}
|
||||
@ -153,8 +158,8 @@ function reload(event) {
|
||||
}
|
||||
|
||||
if (reloadToolbox) {
|
||||
// Reopen the toolbox automatically if we are reloading from toolbox shortcut
|
||||
// and are on a browser window.
|
||||
// Reopen the toolbox automatically if we are reloading from toolbox
|
||||
// shortcut and are on a browser window.
|
||||
// Wait for a second before opening the toolbox to avoid races
|
||||
// between the old and the new one.
|
||||
let {setTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {});
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {createNode, TimeScale} = require("devtools/client/animationinspector/utils");
|
||||
const {Keyframes} = require("devtools/client/animationinspector/components/keyframes");
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
const {Cu} = require("chrome");
|
||||
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
|
||||
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {DomNodePreview} = require("devtools/client/inspector/shared/dom-node-preview");
|
||||
|
||||
// Map dom node fronts by animation fronts so we don't have to get them from the
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {createNode, TimeScale} = require("devtools/client/animationinspector/utils");
|
||||
|
||||
const { LocalizationHelper } = require("devtools/client/shared/l10n");
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {
|
||||
createNode,
|
||||
findOptimalTimeInterval,
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {createNode} = require("devtools/client/animationinspector/utils");
|
||||
|
||||
/**
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {createNode} = require("devtools/client/animationinspector/utils");
|
||||
const { LocalizationHelper } = require("devtools/client/shared/l10n");
|
||||
|
||||
|
@ -11,6 +11,7 @@ const { ObjectClient } = require("devtools/shared/client/main");
|
||||
|
||||
const promise = require("promise");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
|
||||
/**
|
||||
* This object represents DOM panel. It's responsibility is to
|
||||
|
@ -7,9 +7,9 @@
|
||||
const {Cu} = require("chrome");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
var Services = require("Services");
|
||||
var {Task} = require("resource://gre/modules/Task.jsm");
|
||||
var EventEmitter = require("devtools/shared/event-emitter");
|
||||
var Telemetry = require("devtools/client/shared/telemetry");
|
||||
|
||||
|
@ -6,8 +6,6 @@
|
||||
// Test support methods on Target, such as `hasActor`, `getActorDescription`,
|
||||
// `actorHasMethod` and `getTrait`.
|
||||
|
||||
var { Task } =
|
||||
Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
var { WebAudioFront } =
|
||||
require("devtools/server/actors/webaudio");
|
||||
|
||||
|
@ -27,6 +27,7 @@ const {TargetFactory} = require("devtools/client/framework/target");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
let promise = require("promise");
|
||||
const Services = require("Services");
|
||||
const {Task} = require("resource://gre/modules/Task.jsm");
|
||||
|
||||
const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
|
||||
const CHROME_URL_ROOT = TEST_DIR + "/";
|
||||
@ -420,3 +421,21 @@ function waitForContextMenu(popup, button, onShown, onHidden) {
|
||||
button.ownerDocument.defaultView);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper to push a temporary preference. Wrapper on SpecialPowers
|
||||
* pushPrefEnv that returns a promise resolving when the preferences have been
|
||||
* updated.
|
||||
*
|
||||
* @param {String} preferenceName
|
||||
* The name of the preference to updated
|
||||
* @param {} value
|
||||
* The preference value, type can vary
|
||||
* @return {Promise} resolves when the preferences have been updated
|
||||
*/
|
||||
function pushPref(preferenceName, value) {
|
||||
return new Promise(resolve => {
|
||||
let options = {"set": [[preferenceName, value]]};
|
||||
SpecialPowers.pushPrefEnv(options, resolve);
|
||||
});
|
||||
}
|
||||
|
@ -6,9 +6,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
const promise = require("promise");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
const {Task} = require("resource://gre/modules/Task.jsm");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
/**
|
||||
|
@ -6,11 +6,10 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
const Services = require("Services");
|
||||
const promise = require("promise");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const {Task} = require("resource://gre/modules/Task.jsm");
|
||||
const {gDevTools} = require("devtools/client/framework/devtools");
|
||||
|
||||
exports.OptionsPanel = OptionsPanel;
|
||||
|
@ -19,6 +19,7 @@ const SCREENSIZE_HISTOGRAM = "DEVTOOLS_SCREEN_RESOLUTION_ENUMERATED_PER_USER";
|
||||
var {Cc, Ci, Cu} = require("chrome");
|
||||
var promise = require("promise");
|
||||
var Services = require("Services");
|
||||
var {Task} = require("resource://gre/modules/Task.jsm");
|
||||
var {gDevTools} = require("devtools/client/framework/devtools");
|
||||
var EventEmitter = require("devtools/shared/event-emitter");
|
||||
var Telemetry = require("devtools/client/shared/telemetry");
|
||||
@ -28,7 +29,6 @@ var { attachThread, detachThread } = require("./attach-thread");
|
||||
|
||||
Cu.import("resource://devtools/client/scratchpad/scratchpad-manager.jsm");
|
||||
Cu.import("resource://devtools/client/shared/DOMHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
loader.lazyGetter(this, "toolboxStrings", () => {
|
||||
const properties = "chrome://devtools/locale/toolbox.properties";
|
||||
|
@ -6,5 +6,6 @@
|
||||
|
||||
DevToolsModules(
|
||||
'storage.js',
|
||||
'styles.js',
|
||||
'stylesheets.js'
|
||||
)
|
||||
|
415
devtools/client/fronts/styles.js
Normal file
415
devtools/client/fronts/styles.js
Normal file
@ -0,0 +1,415 @@
|
||||
/* 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 {
|
||||
Front,
|
||||
FrontClassWithSpec,
|
||||
custom,
|
||||
preEvent
|
||||
} = require("devtools/shared/protocol.js");
|
||||
const {
|
||||
pageStyleSpec,
|
||||
styleRuleSpec
|
||||
} = require("devtools/shared/specs/styles.js");
|
||||
const promise = require("promise");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { Class } = require("sdk/core/heritage");
|
||||
|
||||
loader.lazyGetter(this, "RuleRewriter", () => {
|
||||
return require("devtools/client/shared/css-parsing-utils").RuleRewriter;
|
||||
});
|
||||
|
||||
/**
|
||||
* PageStyleFront, the front object for the PageStyleActor
|
||||
*/
|
||||
const PageStyleFront = FrontClassWithSpec(pageStyleSpec, {
|
||||
initialize: function (conn, form, ctx, detail) {
|
||||
Front.prototype.initialize.call(this, conn, form, ctx, detail);
|
||||
this.inspector = this.parent();
|
||||
},
|
||||
|
||||
form: function (form, detail) {
|
||||
if (detail === "actorid") {
|
||||
this.actorID = form;
|
||||
return;
|
||||
}
|
||||
this._form = form;
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
Front.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
get walker() {
|
||||
return this.inspector.walker;
|
||||
},
|
||||
|
||||
get supportsAuthoredStyles() {
|
||||
return this._form.traits && this._form.traits.authoredStyles;
|
||||
},
|
||||
|
||||
getMatchedSelectors: custom(function (node, property, options) {
|
||||
return this._getMatchedSelectors(node, property, options).then(ret => {
|
||||
return ret.matched;
|
||||
});
|
||||
}, {
|
||||
impl: "_getMatchedSelectors"
|
||||
}),
|
||||
|
||||
getApplied: custom(Task.async(function* (node, options = {}) {
|
||||
// If the getApplied method doesn't recreate the style cache itself, this
|
||||
// means a call to cssLogic.highlight is required before trying to access
|
||||
// the applied rules. Issue a request to getLayout if this is the case.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1103993#c16.
|
||||
if (!this._form.traits || !this._form.traits.getAppliedCreatesStyleCache) {
|
||||
yield this.getLayout(node);
|
||||
}
|
||||
let ret = yield this._getApplied(node, options);
|
||||
return ret.entries;
|
||||
}), {
|
||||
impl: "_getApplied"
|
||||
}),
|
||||
|
||||
addNewRule: custom(function (node, pseudoClasses) {
|
||||
let addPromise;
|
||||
if (this.supportsAuthoredStyles) {
|
||||
addPromise = this._addNewRule(node, pseudoClasses, true);
|
||||
} else {
|
||||
addPromise = this._addNewRule(node, pseudoClasses);
|
||||
}
|
||||
return addPromise.then(ret => {
|
||||
return ret.entries[0];
|
||||
});
|
||||
}, {
|
||||
impl: "_addNewRule"
|
||||
})
|
||||
});
|
||||
|
||||
exports.PageStyleFront = PageStyleFront;
|
||||
|
||||
/**
|
||||
* StyleRuleFront, the front for the StyleRule actor.
|
||||
*/
|
||||
const StyleRuleFront = FrontClassWithSpec(styleRuleSpec, {
|
||||
initialize: function (client, form, ctx, detail) {
|
||||
Front.prototype.initialize.call(this, client, form, ctx, detail);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
Front.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
form: function (form, detail) {
|
||||
if (detail === "actorid") {
|
||||
this.actorID = form;
|
||||
return;
|
||||
}
|
||||
this.actorID = form.actor;
|
||||
this._form = form;
|
||||
if (this._mediaText) {
|
||||
this._mediaText = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensure _form is updated when location-changed is emitted.
|
||||
*/
|
||||
_locationChangedPre: preEvent("location-changed", function (line, column) {
|
||||
this._clearOriginalLocation();
|
||||
this._form.line = line;
|
||||
this._form.column = column;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Return a new RuleModificationList or RuleRewriter for this node.
|
||||
* A RuleRewriter will be returned when the rule's canSetRuleText
|
||||
* trait is true; otherwise a RuleModificationList will be
|
||||
* returned.
|
||||
*/
|
||||
startModifyingProperties: function () {
|
||||
if (this.canSetRuleText) {
|
||||
return new RuleRewriter(this, this.authoredText);
|
||||
}
|
||||
return new RuleModificationList(this);
|
||||
},
|
||||
|
||||
get type() {
|
||||
return this._form.type;
|
||||
},
|
||||
get line() {
|
||||
return this._form.line || -1;
|
||||
},
|
||||
get column() {
|
||||
return this._form.column || -1;
|
||||
},
|
||||
get cssText() {
|
||||
return this._form.cssText;
|
||||
},
|
||||
get authoredText() {
|
||||
return this._form.authoredText || this._form.cssText;
|
||||
},
|
||||
get keyText() {
|
||||
return this._form.keyText;
|
||||
},
|
||||
get name() {
|
||||
return this._form.name;
|
||||
},
|
||||
get selectors() {
|
||||
return this._form.selectors;
|
||||
},
|
||||
get media() {
|
||||
return this._form.media;
|
||||
},
|
||||
get mediaText() {
|
||||
if (!this._form.media) {
|
||||
return null;
|
||||
}
|
||||
if (this._mediaText) {
|
||||
return this._mediaText;
|
||||
}
|
||||
this._mediaText = this.media.join(", ");
|
||||
return this._mediaText;
|
||||
},
|
||||
|
||||
get parentRule() {
|
||||
return this.conn.getActor(this._form.parentRule);
|
||||
},
|
||||
|
||||
get parentStyleSheet() {
|
||||
return this.conn.getActor(this._form.parentStyleSheet);
|
||||
},
|
||||
|
||||
get element() {
|
||||
return this.conn.getActor(this._form.element);
|
||||
},
|
||||
|
||||
get href() {
|
||||
if (this._form.href) {
|
||||
return this._form.href;
|
||||
}
|
||||
let sheet = this.parentStyleSheet;
|
||||
return sheet ? sheet.href : "";
|
||||
},
|
||||
|
||||
get nodeHref() {
|
||||
let sheet = this.parentStyleSheet;
|
||||
return sheet ? sheet.nodeHref : "";
|
||||
},
|
||||
|
||||
get supportsModifySelectorUnmatched() {
|
||||
return this._form.traits && this._form.traits.modifySelectorUnmatched;
|
||||
},
|
||||
|
||||
get canSetRuleText() {
|
||||
return this._form.traits && this._form.traits.canSetRuleText;
|
||||
},
|
||||
|
||||
get location() {
|
||||
return {
|
||||
source: this.parentStyleSheet,
|
||||
href: this.href,
|
||||
line: this.line,
|
||||
column: this.column
|
||||
};
|
||||
},
|
||||
|
||||
_clearOriginalLocation: function () {
|
||||
this._originalLocation = null;
|
||||
},
|
||||
|
||||
getOriginalLocation: function () {
|
||||
if (this._originalLocation) {
|
||||
return promise.resolve(this._originalLocation);
|
||||
}
|
||||
let parentSheet = this.parentStyleSheet;
|
||||
if (!parentSheet) {
|
||||
// This rule doesn't belong to a stylesheet so it is an inline style.
|
||||
// Inline styles do not have any mediaText so we can return early.
|
||||
return promise.resolve(this.location);
|
||||
}
|
||||
return parentSheet.getOriginalLocation(this.line, this.column)
|
||||
.then(({ fromSourceMap, source, line, column }) => {
|
||||
let location = {
|
||||
href: source,
|
||||
line: line,
|
||||
column: column,
|
||||
mediaText: this.mediaText
|
||||
};
|
||||
if (fromSourceMap === false) {
|
||||
location.source = this.parentStyleSheet;
|
||||
}
|
||||
if (!source) {
|
||||
location.href = this.href;
|
||||
}
|
||||
this._originalLocation = location;
|
||||
return location;
|
||||
});
|
||||
},
|
||||
|
||||
modifySelector: custom(Task.async(function* (node, value) {
|
||||
let response;
|
||||
if (this.supportsModifySelectorUnmatched) {
|
||||
// If the debugee supports adding unmatched rules (post FF41)
|
||||
if (this.canSetRuleText) {
|
||||
response = yield this.modifySelector2(node, value, true);
|
||||
} else {
|
||||
response = yield this.modifySelector2(node, value);
|
||||
}
|
||||
} else {
|
||||
response = yield this._modifySelector(value);
|
||||
}
|
||||
|
||||
if (response.ruleProps) {
|
||||
response.ruleProps = response.ruleProps.entries[0];
|
||||
}
|
||||
return response;
|
||||
}), {
|
||||
impl: "_modifySelector"
|
||||
}),
|
||||
|
||||
setRuleText: custom(function (newText) {
|
||||
this._form.authoredText = newText;
|
||||
return this._setRuleText(newText);
|
||||
}, {
|
||||
impl: "_setRuleText"
|
||||
})
|
||||
});
|
||||
|
||||
exports.StyleRuleFront = StyleRuleFront;
|
||||
|
||||
/**
|
||||
* Convenience API for building a list of attribute modifications
|
||||
* for the `modifyProperties` request. A RuleModificationList holds a
|
||||
* list of modifications that will be applied to a StyleRuleActor.
|
||||
* The modifications are processed in the order in which they are
|
||||
* added to the RuleModificationList.
|
||||
*
|
||||
* Objects of this type expose the same API as @see RuleRewriter.
|
||||
* This lets the inspector use (mostly) the same code, regardless of
|
||||
* whether the server implements setRuleText.
|
||||
*/
|
||||
var RuleModificationList = Class({
|
||||
/**
|
||||
* Initialize a RuleModificationList.
|
||||
* @param {StyleRuleFront} rule the associated rule
|
||||
*/
|
||||
initialize: function (rule) {
|
||||
this.rule = rule;
|
||||
this.modifications = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply the modifications in this object to the associated rule.
|
||||
*
|
||||
* @return {Promise} A promise which will be resolved when the modifications
|
||||
* are complete; @see StyleRuleActor.modifyProperties.
|
||||
*/
|
||||
apply: function () {
|
||||
return this.rule.modifyProperties(this.modifications);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a "set" entry to the modification list.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name the property's name
|
||||
* @param {String} value the property's value
|
||||
* @param {String} priority the property's priority, either the empty
|
||||
* string or "important"
|
||||
*/
|
||||
setProperty: function (index, name, value, priority) {
|
||||
this.modifications.push({
|
||||
type: "set",
|
||||
name: name,
|
||||
value: value,
|
||||
priority: priority
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a "remove" entry to the modification list.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name the name of the property to remove
|
||||
*/
|
||||
removeProperty: function (index, name) {
|
||||
this.modifications.push({
|
||||
type: "remove",
|
||||
name: name
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Rename a property. This implementation acts like
|
||||
* |removeProperty|, because |setRuleText| is not available.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name current name of the property
|
||||
*
|
||||
* This parameter is also passed, but as it is not used in this
|
||||
* implementation, it is omitted. It is documented here as this
|
||||
* code also defined the interface implemented by @see RuleRewriter.
|
||||
* @param {String} newName new name of the property
|
||||
*/
|
||||
renameProperty: function (index, name) {
|
||||
this.removeProperty(index, name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable or disable a property. This implementation acts like
|
||||
* |removeProperty| when disabling, or a no-op when enabling,
|
||||
* because |setRuleText| is not available.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name current name of the property
|
||||
* @param {Boolean} isEnabled true if the property should be enabled;
|
||||
* false if it should be disabled
|
||||
*/
|
||||
setPropertyEnabled: function (index, name, isEnabled) {
|
||||
if (!isEnabled) {
|
||||
this.removeProperty(index, name);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new property. This implementation does nothing, because
|
||||
* |setRuleText| is not available.
|
||||
*
|
||||
* These parameter are passed, but as they are not used in this
|
||||
* implementation, they are omitted. They are documented here as
|
||||
* this code also defined the interface implemented by @see
|
||||
* RuleRewriter.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name name of the new property
|
||||
* @param {String} value value of the new property
|
||||
* @param {String} priority priority of the new property; either
|
||||
* the empty string or "important"
|
||||
*/
|
||||
createProperty: function () {
|
||||
// Nothing.
|
||||
},
|
||||
});
|
||||
|
@ -3,7 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Front, FrontClassWithSpec } = require("devtools/shared/protocol.js");
|
||||
const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
|
||||
const {
|
||||
getIndentationFromPrefs,
|
||||
getIndentationFromString
|
||||
@ -13,9 +13,10 @@ const {
|
||||
mediaRuleSpec,
|
||||
styleSheetSpec,
|
||||
styleSheetsSpec
|
||||
} = require("devtools/shared/specs/stylesheets.js");
|
||||
} = require("devtools/shared/specs/stylesheets");
|
||||
const promise = require("promise");
|
||||
const events = require("sdk/event/core.js");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const events = require("sdk/event/core");
|
||||
|
||||
/**
|
||||
* The client-side counterpart for an OriginalSourceActor.
|
||||
|
@ -7,12 +7,12 @@
|
||||
"use strict";
|
||||
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
const {Task} = require("resource://gre/modules/Task.jsm");
|
||||
const {InplaceEditor, editableItem} =
|
||||
require("devtools/client/shared/inplace-editor");
|
||||
const {ReflowFront} = require("devtools/server/actors/layout");
|
||||
const {LocalizationHelper} = require("devtools/client/shared/l10n");
|
||||
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
|
||||
|
||||
const STRINGS_URI = "chrome://devtools/locale/shared.properties";
|
||||
|
@ -4,10 +4,9 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
const Editor = require("devtools/client/sourceeditor/editor");
|
||||
const Services = require("Services");
|
||||
Cu.import("resource://devtools/shared/event-emitter.js");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
/**
|
||||
* A wrapper around the Editor component, that allows editing of HTML.
|
||||
|
@ -7,9 +7,11 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
const {Cc, Ci} = require("chrome");
|
||||
const promise = require("promise");
|
||||
const Services = require("Services");
|
||||
const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const {Task} = require("resource://gre/modules/Task.jsm");
|
||||
const {Tools} = require("devtools/client/definitions");
|
||||
const {CssLogic} = require("devtools/shared/inspector/css-logic");
|
||||
const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
|
||||
@ -25,8 +27,6 @@ const {createChild, promiseWarn} =
|
||||
require("devtools/client/inspector/shared/utils");
|
||||
const {gDevTools} = require("devtools/client/framework/devtools");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
loader.lazyRequireGetter(this, "overlays",
|
||||
"devtools/client/inspector/shared/style-inspector-overlays");
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"use strict";
|
||||
|
||||
const {Ci} = require("chrome");
|
||||
const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const {CssLogic} = require("devtools/shared/inspector/css-logic");
|
||||
const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
|
||||
const {PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils");
|
||||
|
@ -6,6 +6,7 @@
|
||||
const {Cu} = require("chrome");
|
||||
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
|
||||
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {createNode} = require("devtools/client/animationinspector/utils");
|
||||
const { LocalizationHelper } = require("devtools/client/shared/l10n");
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
const {PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils");
|
||||
const Services = require("Services");
|
||||
const {Task} = require("resource://gre/modules/Task.jsm");
|
||||
|
||||
loader.lazyRequireGetter(this, "overlays",
|
||||
"devtools/client/inspector/shared/style-inspector-overlays");
|
||||
|
@ -4,6 +4,8 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* eslint no-unused-vars: [2, {"vars": "local"}] */
|
||||
/* import-globals-from ../../framework/test/shared-head.js */
|
||||
/* import-globals-from ../../commandline/test/helpers.js */
|
||||
/* import-globals-from ../../shared/test/test-actor-registry.js */
|
||||
"use strict";
|
||||
|
||||
// Load the shared-head file first.
|
||||
|
@ -127,8 +127,8 @@ devtools.jar:
|
||||
content/framework/connect/connect.js (framework/connect/connect.js)
|
||||
content/shared/widgets/graphs-frame.xhtml (shared/widgets/graphs-frame.xhtml)
|
||||
content/shared/widgets/spectrum-frame.xhtml (shared/widgets/spectrum-frame.xhtml)
|
||||
content/shared/widgets/spectrum.css (shared/widgets/spectrum.css)
|
||||
content/shared/widgets/cubic-bezier-frame.xhtml (shared/widgets/cubic-bezier-frame.xhtml)
|
||||
content/shared/widgets/tooltip-frame.xhtml (shared/widgets/tooltip-frame.xhtml)
|
||||
content/shared/widgets/cubic-bezier.css (shared/widgets/cubic-bezier.css)
|
||||
content/shared/widgets/mdn-docs-frame.xhtml (shared/widgets/mdn-docs-frame.xhtml)
|
||||
content/shared/widgets/mdn-docs.css (shared/widgets/mdn-docs.css)
|
||||
@ -195,6 +195,7 @@ devtools.jar:
|
||||
skin/images/breadcrumbs-scrollbutton.png (themes/images/breadcrumbs-scrollbutton.png)
|
||||
skin/images/breadcrumbs-scrollbutton@2x.png (themes/images/breadcrumbs-scrollbutton@2x.png)
|
||||
skin/animationinspector.css (themes/animationinspector.css)
|
||||
skin/spectrum.css (themes/spectrum.css)
|
||||
skin/eyedropper.css (themes/eyedropper.css)
|
||||
skin/canvasdebugger.css (themes/canvasdebugger.css)
|
||||
skin/debugger.css (themes/debugger.css)
|
||||
|
@ -5,7 +5,6 @@
|
||||
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://testing-common/Assert.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
Cu.import("resource://devtools/client/shared/browser-loader.js");
|
||||
var { require } = BrowserLoader({
|
||||
@ -13,6 +12,7 @@ var { require } = BrowserLoader({
|
||||
window: this
|
||||
});
|
||||
var Services = require("Services");
|
||||
var { Task } = require("resource://gre/modules/Task.jsm");
|
||||
|
||||
var EXPECTED_DTU_ASSERT_FAILURE_COUNT = 0;
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
/* 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 { Cc, Ci } = require("chrome");
|
||||
const Services = require("Services");
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const { resolve } = require("promise");
|
||||
const { HarUtils } = require("./har-utils.js");
|
||||
const { HarBuilder } = require("./har-builder.js");
|
||||
|
@ -1,9 +1,11 @@
|
||||
/* 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 { Ci, Cc, CC } = require("chrome");
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "dirService", function () {
|
||||
return Cc["@mozilla.org/file/directory_service;1"]
|
||||
|
@ -108,7 +108,6 @@ const ACTIVITY_TYPE = {
|
||||
DISABLE_CACHE: 4
|
||||
};
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
|
||||
Cu.import("resource://devtools/client/shared/widgets/VariablesView.jsm");
|
||||
Cu.import("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
|
||||
@ -116,6 +115,7 @@ Cu.import("resource://devtools/client/shared/widgets/VariablesViewController.jsm
|
||||
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const promise = require("promise");
|
||||
const Services = require("Services");
|
||||
const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const Editor = require("devtools/client/sourceeditor/editor");
|
||||
const {TimelineFront} = require("devtools/server/actors/timeline");
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
const promise = require("promise");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
|
||||
function NetMonitorPanel(iframeWindow, toolbox) {
|
||||
this.panelWin = iframeWindow;
|
||||
|
@ -6,11 +6,11 @@
|
||||
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://testing-common/Assert.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
var { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
var { require } = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
|
||||
var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
|
||||
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
var { Task } = require("resource://gre/modules/Task.jsm");
|
||||
var { TargetFactory } = require("devtools/client/framework/target");
|
||||
var { Toolbox } = require("devtools/client/framework/toolbox");
|
||||
|
||||
@ -105,9 +105,9 @@ let OPTS_DATA_GENERAL = [{
|
||||
mirType: "Object",
|
||||
typeset: [
|
||||
{ id: 2, keyedBy: "primitive" },
|
||||
{ id: 2, keyedBy: "constructor", name: "B", location: "http://mypage.com/file.js", line: "2" },
|
||||
{ id: 2, keyedBy: "constructor", name: "C", location: "http://mypage.com/file.js", line: "3" },
|
||||
{ id: 2, keyedBy: "constructor", name: "D", location: "http://mypage.com/file.js", line: "4" },
|
||||
{ id: 2, keyedBy: "constructor", name: "B", location: "http://mypage.com/file.js", line: "2" },
|
||||
{ id: 2, keyedBy: "constructor", name: "C", location: "http://mypage.com/file.js", line: "3" },
|
||||
{ id: 2, keyedBy: "constructor", name: "D", location: "http://mypage.com/file.js", line: "4" },
|
||||
],
|
||||
}]
|
||||
}
|
||||
@ -181,4 +181,3 @@ function checkOptimizationTree (rowData) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@
|
||||
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://testing-common/Assert.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
var { gDevTools } = require("devtools/client/framework/devtools");
|
||||
@ -15,6 +14,7 @@ var Services = require("Services");
|
||||
var { DebuggerServer } = require("devtools/server/main");
|
||||
var { DebuggerClient } = require("devtools/shared/client/main");
|
||||
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
var { Task } = require("resource://gre/modules/Task.jsm");
|
||||
var { TargetFactory } = require("devtools/client/framework/target");
|
||||
var { Toolbox } = require("devtools/client/framework/toolbox");
|
||||
|
||||
|
@ -10,6 +10,7 @@ support-files =
|
||||
browser_devices.json
|
||||
doc_options-view.xul
|
||||
head.js
|
||||
helper_html_tooltip.js
|
||||
html-mdn-css-basic-testing.html
|
||||
html-mdn-css-no-summary.html
|
||||
html-mdn-css-no-summary-or-syntax.html
|
||||
@ -111,6 +112,11 @@ skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
|
||||
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
|
||||
[browser_graphs-16.js]
|
||||
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
|
||||
[browser_html_tooltip-01.js]
|
||||
[browser_html_tooltip-02.js]
|
||||
[browser_html_tooltip-03.js]
|
||||
[browser_html_tooltip-04.js]
|
||||
[browser_html_tooltip-05.js]
|
||||
[browser_inplace-editor-01.js]
|
||||
[browser_inplace-editor-02.js]
|
||||
[browser_inplace-editor_maxwidth.js]
|
||||
|
71
devtools/client/shared/test/browser_html_tooltip-01.js
Normal file
71
devtools/client/shared/test/browser_html_tooltip-01.js
Normal file
@ -0,0 +1,71 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from helper_html_tooltip.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the HTMLTooltip show & hide methods.
|
||||
*/
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/skin/common.css"?>
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Tooltip test">
|
||||
<vbox flex="1">
|
||||
<hbox id="box1" flex="1">test1</hbox>
|
||||
<hbox id="box2" flex="1">test2</hbox>
|
||||
<hbox id="box3" flex="1">test3</hbox>
|
||||
<hbox id="box4" flex="1">test4</hbox>
|
||||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
|
||||
function getTooltipContent(doc) {
|
||||
let div = doc.createElementNS(HTML_NS, "div");
|
||||
div.style.height = "50px";
|
||||
div.style.boxSizing = "border-box";
|
||||
div.textContent = "tooltip";
|
||||
return div;
|
||||
}
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("about:blank");
|
||||
let [,, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
let tooltip = new HTMLTooltip({doc}, {});
|
||||
|
||||
info("Set tooltip content");
|
||||
yield tooltip.setContent(getTooltipContent(doc), 100, 50);
|
||||
|
||||
is(tooltip.isVisible(), false, "Tooltip is not visible");
|
||||
|
||||
info("Show the tooltip and check the expected events are fired.");
|
||||
|
||||
let shown = 0;
|
||||
tooltip.on("shown", () => shown++);
|
||||
|
||||
let onShown = tooltip.once("shown");
|
||||
tooltip.show(doc.getElementById("box1"));
|
||||
|
||||
yield onShown;
|
||||
is(shown, 1, "Event shown was fired once");
|
||||
|
||||
is(tooltip.isVisible(), true, "Tooltip is visible");
|
||||
|
||||
info("Hide the tooltip and check the expected events are fired.");
|
||||
|
||||
let hidden = 0;
|
||||
tooltip.on("hidden", () => hidden++);
|
||||
|
||||
let onPopupHidden = tooltip.once("hidden");
|
||||
tooltip.hide();
|
||||
|
||||
yield onPopupHidden;
|
||||
is(hidden, 1, "Event hidden was fired once");
|
||||
|
||||
is(tooltip.isVisible(), false, "Tooltip is not visible");
|
||||
});
|
98
devtools/client/shared/test/browser_html_tooltip-02.js
Normal file
98
devtools/client/shared/test/browser_html_tooltip-02.js
Normal file
@ -0,0 +1,98 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from helper_html_tooltip.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the HTMLTooltip is closed when clicking outside of its container.
|
||||
*/
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Tooltip test">
|
||||
<vbox flex="1">
|
||||
<hbox id="box1" flex="1">test1</hbox>
|
||||
<hbox id="box2" flex="1">test2</hbox>
|
||||
<hbox id="box3" flex="1">test3</hbox>
|
||||
<hbox id="box4" flex="1">test4</hbox>
|
||||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("about:blank");
|
||||
let [,, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
yield testTooltipNotClosingOnInsideClick(doc);
|
||||
yield testConsumeOutsideClicksFalse(doc);
|
||||
yield testConsumeOutsideClicksTrue(doc);
|
||||
});
|
||||
|
||||
function* testTooltipNotClosingOnInsideClick(doc) {
|
||||
info("Test a tooltip is not closed when clicking inside itself");
|
||||
|
||||
let tooltip = new HTMLTooltip({doc}, {});
|
||||
yield tooltip.setContent(getTooltipContent(doc), 100, 50);
|
||||
yield showTooltip(tooltip, doc.getElementById("box1"));
|
||||
|
||||
let onTooltipContainerClick = once(tooltip.container, "click");
|
||||
EventUtils.synthesizeMouseAtCenter(tooltip.container, {}, doc.defaultView);
|
||||
yield onTooltipContainerClick;
|
||||
is(tooltip.isVisible(), true, "Tooltip is still visible");
|
||||
|
||||
tooltip.destroy();
|
||||
}
|
||||
|
||||
function* testConsumeOutsideClicksFalse(doc) {
|
||||
info("Test closing a tooltip via click with consumeOutsideClicks: false");
|
||||
let box4 = doc.getElementById("box4");
|
||||
|
||||
let tooltip = new HTMLTooltip({doc}, {consumeOutsideClicks: false});
|
||||
yield tooltip.setContent(getTooltipContent(doc), 100, 50);
|
||||
yield showTooltip(tooltip, doc.getElementById("box1"));
|
||||
|
||||
let onBox4Clicked = once(box4, "click");
|
||||
let onHidden = once(tooltip, "hidden");
|
||||
EventUtils.synthesizeMouseAtCenter(box4, {}, doc.defaultView);
|
||||
yield onHidden;
|
||||
yield onBox4Clicked;
|
||||
|
||||
is(tooltip.isVisible(), false, "Tooltip is hidden");
|
||||
|
||||
tooltip.destroy();
|
||||
}
|
||||
|
||||
function* testConsumeOutsideClicksTrue(doc) {
|
||||
info("Test closing a tooltip via click with consumeOutsideClicks: true");
|
||||
let box4 = doc.getElementById("box4");
|
||||
|
||||
// Count clicks on box4
|
||||
let box4clicks = 0;
|
||||
box4.addEventListener("click", () => box4clicks++);
|
||||
|
||||
let tooltip = new HTMLTooltip({doc}, {consumeOutsideClicks: true});
|
||||
yield tooltip.setContent(getTooltipContent(doc), 100, 50);
|
||||
yield showTooltip(tooltip, doc.getElementById("box1"));
|
||||
|
||||
let onHidden = once(tooltip, "hidden");
|
||||
EventUtils.synthesizeMouseAtCenter(box4, {}, doc.defaultView);
|
||||
yield onHidden;
|
||||
|
||||
is(box4clicks, 0, "box4 catched no click event");
|
||||
is(tooltip.isVisible(), false, "Tooltip is hidden");
|
||||
|
||||
tooltip.destroy();
|
||||
}
|
||||
|
||||
function getTooltipContent(doc) {
|
||||
let div = doc.createElementNS(HTML_NS, "div");
|
||||
div.style.height = "50px";
|
||||
div.style.boxSizing = "border-box";
|
||||
div.textContent = "tooltip";
|
||||
return div;
|
||||
}
|
93
devtools/client/shared/test/browser_html_tooltip-03.js
Normal file
93
devtools/client/shared/test/browser_html_tooltip-03.js
Normal file
@ -0,0 +1,93 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from helper_html_tooltip.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the HTMLTooltip autofocus configuration option.
|
||||
*/
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Tooltip test">
|
||||
<vbox flex="1">
|
||||
<hbox id="box1" flex="1">test1</hbox>
|
||||
<hbox id="box2" flex="1">test2</hbox>
|
||||
<hbox id="box3" flex="1">test3</hbox>
|
||||
<hbox id="box4" flex="1">
|
||||
<textbox id="box4-input"></textbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("about:blank");
|
||||
let [,, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
yield testTooltipWithAutoFocus(doc);
|
||||
yield testTooltipWithoutAutoFocus(doc);
|
||||
});
|
||||
|
||||
function* testTooltipWithAutoFocus(doc) {
|
||||
info("Test a tooltip with autofocus takes focus when displayed");
|
||||
let textbox = doc.querySelector("textbox");
|
||||
|
||||
info("Focus a XUL textbox");
|
||||
let onInputFocus = once(textbox, "focus");
|
||||
EventUtils.synthesizeMouseAtCenter(textbox, {}, doc.defaultView);
|
||||
yield onInputFocus;
|
||||
|
||||
is(getFocusedDocument(doc), doc, "Focus is in the XUL document");
|
||||
|
||||
let tooltip = new HTMLTooltip({doc}, {autofocus: true});
|
||||
let tooltipNode = getTooltipContent(doc);
|
||||
yield tooltip.setContent(tooltipNode, 150, 50);
|
||||
|
||||
yield showTooltip(tooltip, doc.getElementById("box1"));
|
||||
is(getFocusedDocument(doc), tooltipNode.ownerDocument,
|
||||
"Focus is in the tooltip document");
|
||||
|
||||
yield hideTooltip(tooltip);
|
||||
}
|
||||
|
||||
function* testTooltipWithoutAutoFocus(doc) {
|
||||
info("Test a tooltip can be closed by clicking outside");
|
||||
let textbox = doc.querySelector("textbox");
|
||||
|
||||
info("Focus a XUL textbox");
|
||||
let onInputFocus = once(textbox, "focus");
|
||||
EventUtils.synthesizeMouseAtCenter(textbox, {}, doc.defaultView);
|
||||
yield onInputFocus;
|
||||
|
||||
is(getFocusedDocument(doc), doc, "Focus is in the XUL document");
|
||||
|
||||
let tooltip = new HTMLTooltip({doc}, {autofocus: false});
|
||||
let tooltipNode = getTooltipContent(doc);
|
||||
yield tooltip.setContent(tooltipNode, 150, 50);
|
||||
|
||||
yield showTooltip(tooltip, doc.getElementById("box1"));
|
||||
is(getFocusedDocument(doc), doc, "Focus is still in the XUL document");
|
||||
|
||||
yield hideTooltip(tooltip);
|
||||
}
|
||||
|
||||
function getFocusedDocument(doc) {
|
||||
let activeElement = doc.activeElement;
|
||||
while (activeElement && activeElement.contentDocument) {
|
||||
activeElement = activeElement.contentDocument.activeElement;
|
||||
}
|
||||
return activeElement.ownerDocument;
|
||||
}
|
||||
|
||||
function getTooltipContent(doc) {
|
||||
let div = doc.createElementNS(HTML_NS, "div");
|
||||
div.style.height = "50px";
|
||||
div.style.boxSizing = "border-box";
|
||||
return div;
|
||||
}
|
108
devtools/client/shared/test/browser_html_tooltip-04.js
Normal file
108
devtools/client/shared/test/browser_html_tooltip-04.js
Normal file
@ -0,0 +1,108 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from helper_html_tooltip.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the HTMLTooltip positioning for a small tooltip element (should aways
|
||||
* find a way to fit).
|
||||
*/
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/skin/common.css"?>
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Tooltip test">
|
||||
<vbox flex="1">
|
||||
<hbox style="height: 10px">spacer</hbox>
|
||||
<hbox id="box1" style="height: 50px">test1</hbox>
|
||||
<hbox id="box2" style="height: 50px">test2</hbox>
|
||||
<hbox flex="1">MIDDLE</hbox>
|
||||
<hbox id="box3" style="height: 50px">test3</hbox>
|
||||
<hbox id="box4" style="height: 50px">test4</hbox>
|
||||
<hbox style="height: 10px">spacer</hbox>
|
||||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
const TOOLTIP_HEIGHT = 30;
|
||||
const TOOLTIP_WIDTH = 100;
|
||||
|
||||
add_task(function* () {
|
||||
// Force the toolbox to be 400px high;
|
||||
yield pushPref("devtools.toolbox.footer.height", 400);
|
||||
|
||||
yield addTab("about:blank");
|
||||
let [,, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
info("Create HTML tooltip");
|
||||
let tooltip = new HTMLTooltip({doc}, {});
|
||||
let div = doc.createElementNS(HTML_NS, "div");
|
||||
div.style.height = "100%";
|
||||
yield tooltip.setContent(div, TOOLTIP_WIDTH, TOOLTIP_HEIGHT);
|
||||
|
||||
let box1 = doc.getElementById("box1");
|
||||
let box2 = doc.getElementById("box2");
|
||||
let box3 = doc.getElementById("box3");
|
||||
let box4 = doc.getElementById("box4");
|
||||
let height = TOOLTIP_HEIGHT, width = TOOLTIP_WIDTH;
|
||||
|
||||
// box1: Can only fit below box1
|
||||
info("Display the tooltip on box1.");
|
||||
yield showTooltip(tooltip, box1);
|
||||
let expectedTooltipGeometry = {position: "bottom", height, width};
|
||||
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on top of box1.");
|
||||
yield showTooltip(tooltip, box1, "top");
|
||||
expectedTooltipGeometry = {position: "bottom", height, width};
|
||||
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
// box2: Can fit above or below, will default to bottom, more height
|
||||
// available.
|
||||
info("Try to display the tooltip on box2.");
|
||||
yield showTooltip(tooltip, box2);
|
||||
expectedTooltipGeometry = {position: "bottom", height, width};
|
||||
checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on top of box2.");
|
||||
yield showTooltip(tooltip, box2, "top");
|
||||
expectedTooltipGeometry = {position: "top", height, width};
|
||||
checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
// box3: Can fit above or below, will default to top, more height available.
|
||||
info("Try to display the tooltip on box3.");
|
||||
yield showTooltip(tooltip, box3);
|
||||
expectedTooltipGeometry = {position: "top", height, width};
|
||||
checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on bottom of box3.");
|
||||
yield showTooltip(tooltip, box3, "bottom");
|
||||
expectedTooltipGeometry = {position: "bottom", height, width};
|
||||
checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
// box4: Can only fit above box4
|
||||
info("Display the tooltip on box4.");
|
||||
yield showTooltip(tooltip, box4);
|
||||
expectedTooltipGeometry = {position: "top", height, width};
|
||||
checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on bottom of box4.");
|
||||
yield showTooltip(tooltip, box4, "bottom");
|
||||
expectedTooltipGeometry = {position: "top", height, width};
|
||||
checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
is(tooltip.isVisible(), false, "Tooltip is not visible");
|
||||
});
|
108
devtools/client/shared/test/browser_html_tooltip-05.js
Normal file
108
devtools/client/shared/test/browser_html_tooltip-05.js
Normal file
@ -0,0 +1,108 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from helper_html_tooltip.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the HTMLTooltip positioning for a huge tooltip element (can not fit in
|
||||
* the viewport).
|
||||
*/
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/skin/common.css"?>
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Tooltip test">
|
||||
<vbox flex="1">
|
||||
<hbox id="box1" style="height: 50px">test1</hbox>
|
||||
<hbox id="box2" style="height: 50px">test2</hbox>
|
||||
<hbox id="box3" style="height: 50px">test3</hbox>
|
||||
<hbox id="box4" style="height: 50px">test4</hbox>
|
||||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
const TOOLTIP_HEIGHT = 200;
|
||||
const TOOLTIP_WIDTH = 200;
|
||||
|
||||
add_task(function* () {
|
||||
// Force the toolbox to be 200px high;
|
||||
yield pushPref("devtools.toolbox.footer.height", 200);
|
||||
|
||||
yield addTab("about:blank");
|
||||
let [,, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
info("Create HTML tooltip");
|
||||
let tooltip = new HTMLTooltip({doc}, {});
|
||||
let div = doc.createElementNS(HTML_NS, "div");
|
||||
div.style.height = "100%";
|
||||
yield tooltip.setContent(div, TOOLTIP_WIDTH, TOOLTIP_HEIGHT);
|
||||
|
||||
let box1 = doc.getElementById("box1");
|
||||
let box2 = doc.getElementById("box2");
|
||||
let box3 = doc.getElementById("box3");
|
||||
let box4 = doc.getElementById("box4");
|
||||
let width = TOOLTIP_WIDTH;
|
||||
|
||||
// box1: Can not fit above or below box1, default to bottom with a reduced
|
||||
// height of 150px.
|
||||
info("Display the tooltip on box1.");
|
||||
yield showTooltip(tooltip, box1);
|
||||
let expectedTooltipGeometry = {position: "bottom", height: 150, width};
|
||||
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on top of box1.");
|
||||
yield showTooltip(tooltip, box1, "top");
|
||||
expectedTooltipGeometry = {position: "bottom", height: 150, width};
|
||||
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
// box2: Can not fit above or below box2, default to bottom with a reduced
|
||||
// height of 100px.
|
||||
info("Try to display the tooltip on box2.");
|
||||
yield showTooltip(tooltip, box2);
|
||||
expectedTooltipGeometry = {position: "bottom", height: 100, width};
|
||||
checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on top of box2.");
|
||||
yield showTooltip(tooltip, box2, "top");
|
||||
expectedTooltipGeometry = {position: "bottom", height: 100, width};
|
||||
checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
// box3: Can not fit above or below box3, default to top with a reduced height
|
||||
// of 100px.
|
||||
info("Try to display the tooltip on box3.");
|
||||
yield showTooltip(tooltip, box3);
|
||||
expectedTooltipGeometry = {position: "top", height: 100, width};
|
||||
checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on bottom of box3.");
|
||||
yield showTooltip(tooltip, box3, "bottom");
|
||||
expectedTooltipGeometry = {position: "top", height: 100, width};
|
||||
checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
// box4: Can not fit above or below box4, default to top with a reduced height
|
||||
// of 150px.
|
||||
info("Display the tooltip on box4.");
|
||||
yield showTooltip(tooltip, box4);
|
||||
expectedTooltipGeometry = {position: "top", height: 150, width};
|
||||
checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on bottom of box4.");
|
||||
yield showTooltip(tooltip, box4, "bottom");
|
||||
expectedTooltipGeometry = {position: "top", height: 150, width};
|
||||
checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
is(tooltip.isVisible(), false, "Tooltip is not visible");
|
||||
});
|
@ -20,7 +20,6 @@ const TEST_URI = "data:text/xml;charset=UTF-8,<?xml version='1.0'?>" +
|
||||
const TEST_OPT = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
|
||||
|
||||
const {TableWidget} = require("devtools/client/shared/widgets/TableWidget");
|
||||
var {Task} = require("resource://gre/modules/Task.jsm");
|
||||
|
||||
var doc, table;
|
||||
|
||||
|
78
devtools/client/shared/test/helper_html_tooltip.js
Normal file
78
devtools/client/shared/test/helper_html_tooltip.js
Normal file
@ -0,0 +1,78 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Helper methods for the HTMLTooltip integration tests.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Display an existing HTMLTooltip on an anchor. Returns a promise that will
|
||||
* resolve when the tooltip "shown" event has been fired.
|
||||
*
|
||||
* @param {HTMLTooltip} tooltip
|
||||
* The tooltip instance to display
|
||||
* @param {Node} anchor
|
||||
* The anchor that should be used to display the tooltip
|
||||
* @param {String} position
|
||||
* The preferred display position ("top", "bottom")
|
||||
* @return {Promise} promise that resolves when the "shown" event is fired
|
||||
*/
|
||||
function showTooltip(tooltip, anchor, position) {
|
||||
let onShown = tooltip.once("shown");
|
||||
tooltip.show(anchor, {position});
|
||||
return onShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide an existing HTMLTooltip. Returns a promise that will resolve when the
|
||||
* tooltip "hidden" event has been fired.
|
||||
*
|
||||
* @param {HTMLTooltip} tooltip
|
||||
* The tooltip instance to hide
|
||||
* @return {Promise} promise that resolves when the "hidden" event is fired
|
||||
*/
|
||||
function hideTooltip(tooltip) {
|
||||
let onPopupHidden = tooltip.once("hidden");
|
||||
tooltip.hide();
|
||||
return onPopupHidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test helper designed to check that a tooltip is displayed at the expected
|
||||
* position relative to an anchor, given a set of expectations.
|
||||
*
|
||||
* @param {HTMLTooltip} tooltip
|
||||
* The HTMLTooltip instance to check
|
||||
* @param {Node} anchor
|
||||
* The tooltip's anchor
|
||||
* @param {Object} expected
|
||||
* - {String} position : "top" or "bottom"
|
||||
* - {Boolean} leftAligned
|
||||
* - {Number} width: expected tooltip width
|
||||
* - {Number} height: expected tooltip height
|
||||
*/
|
||||
function checkTooltipGeometry(tooltip, anchor,
|
||||
{position, leftAligned = true, height, width} = {}) {
|
||||
info("Check the tooltip geometry matches expected position and dimensions");
|
||||
let tooltipRect = tooltip.container.getBoundingClientRect();
|
||||
let anchorRect = anchor.getBoundingClientRect();
|
||||
|
||||
if (position === "top") {
|
||||
is(tooltipRect.bottom, anchorRect.top, "Tooltip is above the anchor");
|
||||
} else if (position === "bottom") {
|
||||
is(tooltipRect.top, anchorRect.bottom, "Tooltip is below the anchor");
|
||||
} else {
|
||||
ok(false, "Invalid position provided to checkTooltipGeometry");
|
||||
}
|
||||
|
||||
if (leftAligned) {
|
||||
is(tooltipRect.left, anchorRect.left,
|
||||
"Tooltip left-aligned with the anchor");
|
||||
}
|
||||
|
||||
is(tooltipRect.height, height, "Tooltip has the expected height");
|
||||
is(tooltipRect.width, width, "Tooltip has the expected width");
|
||||
}
|
295
devtools/client/shared/widgets/HTMLTooltip.js
Normal file
295
devtools/client/shared/widgets/HTMLTooltip.js
Normal file
@ -0,0 +1,295 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const IFRAME_URL = "chrome://devtools/content/shared/widgets/tooltip-frame.xhtml";
|
||||
const IFRAME_CONTAINER_ID = "tooltip-iframe-container";
|
||||
|
||||
/**
|
||||
* The HTMLTooltip can display HTML content in a tooltip popup.
|
||||
*
|
||||
* @param {Toolbox} toolbox
|
||||
* The devtools toolbox, needed to get the devtools main window.
|
||||
* @param {Object}
|
||||
* - {String} type
|
||||
* Display type of the tooltip. Possible values: "normal"
|
||||
* - {Boolean} autofocus
|
||||
* Defaults to true. Should the tooltip be focused when opening it.
|
||||
* - {Boolean} consumeOutsideClicks
|
||||
* Defaults to true. The tooltip is closed when clicking outside.
|
||||
* Should this event be stopped and consumed or not.
|
||||
*/
|
||||
function HTMLTooltip(toolbox,
|
||||
{type = "normal", autofocus = true, consumeOutsideClicks = true} = {}) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.document = toolbox.doc;
|
||||
this.type = type;
|
||||
this.autofocus = autofocus;
|
||||
this.consumeOutsideClicks = consumeOutsideClicks;
|
||||
|
||||
// Use the topmost window to listen for click events to close the tooltip
|
||||
this.topWindow = this.document.defaultView.top;
|
||||
|
||||
this._onClick = this._onClick.bind(this);
|
||||
|
||||
this.container = this._createContainer();
|
||||
|
||||
// Promise that will resolve when the container can be filled with content.
|
||||
this.containerReady = new Promise(resolve => {
|
||||
if (this._isXUL()) {
|
||||
// In XUL context, load a placeholder document in the iframe container.
|
||||
let onLoad = () => {
|
||||
this.container.removeEventListener("load", onLoad, true);
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.container.addEventListener("load", onLoad, true);
|
||||
this.container.setAttribute("src", IFRAME_URL);
|
||||
} else {
|
||||
// In non-XUL context the container is ready to use as is.
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.HTMLTooltip = HTMLTooltip;
|
||||
|
||||
HTMLTooltip.prototype = {
|
||||
position: {
|
||||
TOP: "top",
|
||||
BOTTOM: "bottom",
|
||||
},
|
||||
|
||||
get parent() {
|
||||
if (this._isXUL()) {
|
||||
// In XUL context, we are wrapping the HTML content in an iframe.
|
||||
let win = this.container.contentWindow.wrappedJSObject;
|
||||
return win.document.getElementById(IFRAME_CONTAINER_ID);
|
||||
}
|
||||
return this.container;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the tooltip content element. The preferred width/height should also be
|
||||
* specified here.
|
||||
*
|
||||
* @param {Element} content
|
||||
* The tooltip content, should be a HTML element.
|
||||
* @param {Number} width
|
||||
* Preferred width for the tooltip container
|
||||
* @param {Number} height
|
||||
* Preferred height for the tooltip container
|
||||
* @return {Promise} a promise that will resolve when the content has been
|
||||
* added in the tooltip container.
|
||||
*/
|
||||
setContent: function (content, width, height) {
|
||||
this.preferredWidth = width;
|
||||
this.preferredHeight = height;
|
||||
|
||||
return this.containerReady.then(() => {
|
||||
this.parent.innerHTML = "";
|
||||
this.parent.appendChild(content);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the tooltip next to the provided anchor element. A preferred position
|
||||
* can be set. The event "shown" will be fired after the tooltip is displayed.
|
||||
*
|
||||
* @param {Element} anchor
|
||||
* The reference element with which the tooltip should be aligned
|
||||
* @param {Object}
|
||||
* - {String} position: optional, possible values: top|bottom
|
||||
* If layout permits, the tooltip will be displayed on top/bottom
|
||||
* of the anchor. If ommitted, the tooltip will be displayed where
|
||||
* more space is available.
|
||||
*/
|
||||
show: function (anchor, {position} = {}) {
|
||||
this.containerReady.then(() => {
|
||||
let {top, left, width, height} = this._findBestPosition(anchor, position);
|
||||
|
||||
if (this._isXUL()) {
|
||||
this.container.setAttribute("width", width);
|
||||
this.container.setAttribute("height", height);
|
||||
} else {
|
||||
this.container.style.width = width + "px";
|
||||
this.container.style.height = height + "px";
|
||||
}
|
||||
|
||||
this.container.style.top = top + "px";
|
||||
this.container.style.left = left + "px";
|
||||
this.container.style.display = "block";
|
||||
|
||||
if (this.autofocus) {
|
||||
this.container.focus();
|
||||
}
|
||||
|
||||
this.attachEventsTimer = this.document.defaultView.setTimeout(() => {
|
||||
this.topWindow.addEventListener("click", this._onClick, true);
|
||||
this.emit("shown");
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the current tooltip. The event "hidden" will be fired when the tooltip
|
||||
* is hidden.
|
||||
*/
|
||||
hide: function () {
|
||||
this.document.defaultView.clearTimeout(this.attachEventsTimer);
|
||||
|
||||
if (this.isVisible()) {
|
||||
this.topWindow.removeEventListener("click", this._onClick, true);
|
||||
this.container.style.display = "none";
|
||||
this.emit("hidden");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the tooltip is currently displayed.
|
||||
* @return {Boolean} true if the tooltip is visible
|
||||
*/
|
||||
isVisible: function () {
|
||||
let win = this.document.defaultView;
|
||||
return win.getComputedStyle(this.container).display != "none";
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the tooltip instance. Hide the tooltip if displayed, remove the
|
||||
* tooltip container from the document.
|
||||
*/
|
||||
destroy: function () {
|
||||
this.hide();
|
||||
this.container.remove();
|
||||
},
|
||||
|
||||
_createContainer: function () {
|
||||
let container;
|
||||
if (this._isXUL()) {
|
||||
container = this.document.createElementNS(XHTML_NS, "iframe");
|
||||
container.classList.add("devtools-tooltip-iframe");
|
||||
this.document.querySelector("window").appendChild(container);
|
||||
} else {
|
||||
container = this.document.createElementNS(XHTML_NS, "div");
|
||||
this.document.body.appendChild(container);
|
||||
}
|
||||
|
||||
container.classList.add("theme-body");
|
||||
container.classList.add("devtools-htmltooltip-container");
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
_onClick: function (e) {
|
||||
if (this._isInTooltipContainer(e.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hide();
|
||||
if (this.consumeOutsideClicks) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
|
||||
_isInTooltipContainer: function (node) {
|
||||
let contentWindow = this.parent.ownerDocument.defaultView;
|
||||
let win = node.ownerDocument.defaultView;
|
||||
|
||||
if (win === contentWindow) {
|
||||
// If node is in the same window as the tooltip, check if the tooltip
|
||||
// parent contains node.
|
||||
return this.parent.contains(node);
|
||||
}
|
||||
|
||||
// Otherwise check if the node window is in the tooltip window.
|
||||
while (win.parent && win.parent != win) {
|
||||
win = win.parent;
|
||||
if (win === contentWindow) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_findBestPosition: function (anchor, position) {
|
||||
let top, left;
|
||||
let {TOP, BOTTOM} = this.position;
|
||||
|
||||
let {left: anchorLeft, top: anchorTop, height: anchorHeight}
|
||||
= this._getRelativeRect(anchor, this.document);
|
||||
|
||||
let {bottom: docBottom, right: docRight} =
|
||||
this.document.documentElement.getBoundingClientRect();
|
||||
|
||||
let height = this.preferredHeight;
|
||||
// Check if the popup can fit above the anchor.
|
||||
let availableTop = anchorTop;
|
||||
let fitsAbove = availableTop >= height;
|
||||
// Check if the popup can fit below the anchor.
|
||||
let availableBelow = docBottom - (anchorTop + anchorHeight);
|
||||
let fitsBelow = availableBelow >= height;
|
||||
|
||||
let isPositionSuitable = (fitsAbove && position === TOP)
|
||||
|| (fitsBelow && position === BOTTOM);
|
||||
if (!isPositionSuitable) {
|
||||
// If the preferred position does not fit the preferred height,
|
||||
// pick the position offering the most height.
|
||||
position = availableTop > availableBelow ? TOP : BOTTOM;
|
||||
}
|
||||
|
||||
// Calculate height, capped by the maximum height available.
|
||||
height = Math.min(height, Math.max(availableTop, availableBelow));
|
||||
top = position === TOP ? anchorTop - height : anchorTop + anchorHeight;
|
||||
|
||||
let availableWidth = docRight;
|
||||
let width = Math.min(this.preferredWidth, availableWidth);
|
||||
|
||||
// By default, align the tooltip's left edge with the anchor left edge.
|
||||
if (anchorLeft + width <= docRight) {
|
||||
left = anchorLeft;
|
||||
} else {
|
||||
// If the tooltip cannot fit, shift to the left just enough to fit.
|
||||
left = docRight - width;
|
||||
}
|
||||
|
||||
return {top, left, width, height};
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the bounding client rectangle for a given node, relative to a custom
|
||||
* reference element (instead of the default for getBoundingClientRect which
|
||||
* is always the element's ownerDocument).
|
||||
*/
|
||||
_getRelativeRect: function (node, relativeTo) {
|
||||
// Width and Height can be taken from the rect.
|
||||
let {width, height} = node.getBoundingClientRect();
|
||||
|
||||
// Find the smallest top/left coordinates from all quads.
|
||||
let top = Infinity, left = Infinity;
|
||||
let quads = node.getBoxQuads({relativeTo: relativeTo});
|
||||
for (let quad of quads) {
|
||||
top = Math.min(top, quad.bounds.top);
|
||||
left = Math.min(left, quad.bounds.left);
|
||||
}
|
||||
|
||||
// Compute right and bottom coordinates using the rest of the data.
|
||||
let right = left + width;
|
||||
let bottom = top + height;
|
||||
|
||||
return {top, right, bottom, left, width, height};
|
||||
},
|
||||
|
||||
_isXUL: function () {
|
||||
return this.document.documentElement.namespaceURI === XUL_NS;
|
||||
},
|
||||
};
|
@ -645,6 +645,7 @@ Tooltip.prototype = {
|
||||
iframe.setAttribute("width", width);
|
||||
iframe.setAttribute("height", height);
|
||||
iframe.setAttribute("flex", "1");
|
||||
iframe.setAttribute("tooltip", "aHTMLTooltip");
|
||||
iframe.setAttribute("class", "devtools-tooltip-iframe");
|
||||
|
||||
// Wait for the load to initialize the widget
|
||||
|
@ -5,7 +5,6 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
@ -16,9 +15,7 @@ const WIDGET_FOCUSABLE_NODES = new Set(["vbox", "hbox"]);
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const Services = require("Services");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
Cu.import("resource://devtools/shared/event-emitter.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"Heritage", "ViewHelpers", "WidgetMethods",
|
||||
|
@ -20,6 +20,7 @@ DevToolsModules(
|
||||
'FlameGraph.js',
|
||||
'Graphs.js',
|
||||
'GraphsWorker.js',
|
||||
'HTMLTooltip.js',
|
||||
'LineGraphWidget.js',
|
||||
'MdnDocsWidget.js',
|
||||
'MountainGraphWidget.js',
|
||||
|
@ -7,7 +7,7 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/shared/widgets/spectrum.css" ype="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/spectrum.css" type="text/css"/>
|
||||
<script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
|
||||
<style>
|
||||
body {
|
||||
|
24
devtools/client/shared/widgets/tooltip-frame.xhtml
Normal file
24
devtools/client/shared/widgets/tooltip-frame.xhtml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
|
||||
<style>
|
||||
html, body, #tooltip-iframe-container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body role="application" class="theme-body">
|
||||
<div id="tooltip-iframe-container"></div>
|
||||
</body>
|
||||
</html>
|
@ -5,6 +5,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Task} = require("resource://gre/modules/Task.jsm");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {LocalizationHelper} = require("devtools/client/shared/l10n");
|
||||
|
||||
|
@ -17,12 +17,12 @@ const promise = require("promise");
|
||||
const {CssLogic} = require("devtools/shared/inspector/css-logic");
|
||||
const {console} = require("resource://gre/modules/Console.jsm");
|
||||
const Services = require("Services");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
const { TextDecoder, OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://devtools/shared/event-emitter.js");
|
||||
/* import-globals-from StyleEditorUtil.jsm */
|
||||
Cu.import("resource://devtools/client/styleeditor/StyleEditorUtil.jsm");
|
||||
|
||||
|
@ -8,10 +8,10 @@
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
var Services = require("Services");
|
||||
var promise = require("promise");
|
||||
var {Task} = require("resource://gre/modules/Task.jsm");
|
||||
var {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
var EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
Cu.import("resource://devtools/client/styleeditor/StyleEditorUI.jsm");
|
||||
|
@ -6,10 +6,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
const Services = require("Services");
|
||||
|
||||
Cu.import("resource://devtools/shared/event-emitter.js");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
exports.PREF_ORIG_SOURCES = "devtools.styleeditor.source-maps-enabled";
|
||||
|
||||
|
@ -246,6 +246,12 @@
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.devtools-htmltooltip-container {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* links to source code, like displaying `myfile.js:45` */
|
||||
|
||||
.devtools-source-link {
|
||||
|
@ -9,13 +9,13 @@
|
||||
var { utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://testing-common/Assert.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
|
||||
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
var {DebuggerServer} = require("devtools/server/main");
|
||||
var {DebuggerClient} = require("devtools/shared/client/main");
|
||||
var { Task } = require("resource://gre/modules/Task.jsm");
|
||||
var { DebuggerServer } = require("devtools/server/main");
|
||||
var { DebuggerClient } = require("devtools/shared/client/main");
|
||||
|
||||
const Services = require("Services");
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
const TEST_URI = "data:text/html;charset=utf8,<p>test Scratchpad panel " +
|
||||
"linking</p>";
|
||||
|
||||
var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
var { Tools } = require("devtools/client/definitions");
|
||||
var { isTargetSupported } = Tools.scratchpad;
|
||||
|
||||
|
@ -11,8 +11,7 @@ function test() {
|
||||
// Test is slow on Linux EC2 instances - Bug 962931
|
||||
requestLongerTimeout(2);
|
||||
|
||||
let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
let {Toolbox} = require("devtools/client/framework/toolbox")
|
||||
let {Toolbox} = require("devtools/client/framework/toolbox");
|
||||
let toolbox;
|
||||
|
||||
loadTab(TEST_URI).then(testConsoleLoadOnDifferentPanel);
|
||||
|
@ -9,7 +9,6 @@
|
||||
// shared-head.js handles imports, constants, and utility functions
|
||||
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
|
||||
|
||||
var {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
var {Utils: WebConsoleUtils} = require("devtools/shared/webconsole/utils");
|
||||
var {Messages} = require("devtools/client/webconsole/console-output");
|
||||
const asyncStorage = require("devtools/shared/async-storage");
|
||||
|
@ -6,12 +6,12 @@
|
||||
var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const {gDevTools} = require("devtools/client/framework/devtools");
|
||||
const promise = require("promise");
|
||||
const Services = require("Services");
|
||||
const {Task} = require("resource://gre/modules/Task.jsm");
|
||||
const {AppProjects} = require("devtools/client/webide/modules/app-projects");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
DevToolsUtils.testing = true;
|
||||
|
@ -80,6 +80,7 @@ const {getLayoutChangesObserver, releaseLayoutChangesObserver} =
|
||||
loader.lazyRequireGetter(this, "CSS", "CSS");
|
||||
|
||||
const {EventParsers} = require("devtools/shared/event-parsers");
|
||||
const { makeInfallible } = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
|
||||
const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20;
|
||||
@ -151,19 +152,6 @@ loader.lazyGetter(this, "eventListenerService", function () {
|
||||
|
||||
loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic").CssLogic);
|
||||
|
||||
// XXX: A poor man's makeInfallible until we move it out of transport.js
|
||||
// Which should be very soon.
|
||||
function makeInfallible(handler) {
|
||||
return function (...args) {
|
||||
try {
|
||||
return handler.apply(this, args);
|
||||
} catch(ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
// A resolve that hits the main loop first.
|
||||
function delayedResolve(value) {
|
||||
let deferred = promise.defer();
|
||||
|
@ -10,6 +10,7 @@ const protocol = require("devtools/shared/protocol");
|
||||
const {Arg, Option, method, RetVal, types} = protocol;
|
||||
const events = require("sdk/event/core");
|
||||
const {Class} = require("sdk/core/heritage");
|
||||
const {PageStyleFront, StyleRuleFront} = require("devtools/client/fronts/styles");
|
||||
const {LongStringActor} = require("devtools/server/actors/string");
|
||||
const {
|
||||
getDefinedGeometryProperties
|
||||
@ -18,6 +19,7 @@ const {
|
||||
// This will also add the "stylesheet" actor type for protocol.js to recognize
|
||||
const {UPDATE_PRESERVING_RULES, UPDATE_GENERAL} =
|
||||
require("devtools/server/actors/stylesheets");
|
||||
const {pageStyleSpec, styleRuleSpec} = require("devtools/shared/specs/styles");
|
||||
|
||||
loader.lazyRequireGetter(this, "CSS", "CSS");
|
||||
|
||||
@ -28,10 +30,6 @@ loader.lazyGetter(this, "DOMUtils", () => {
|
||||
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||
});
|
||||
|
||||
loader.lazyGetter(this, "RuleRewriter", () => {
|
||||
return require("devtools/client/shared/css-parsing-utils").RuleRewriter;
|
||||
});
|
||||
|
||||
// The PageStyle actor flattens the DOM CSS objects a little bit, merging
|
||||
// Rules and their Styles into one actor. For elements (which have a style
|
||||
// but no associated rule) we fake a rule with the following style id.
|
||||
@ -55,82 +53,11 @@ const BOLD_FONT_WEIGHT = 700;
|
||||
// Offset (in px) to avoid cutting off text edges of italic fonts.
|
||||
const FONT_PREVIEW_OFFSET = 4;
|
||||
|
||||
// Predeclare the domnode actor type for use in requests.
|
||||
types.addActorType("domnode");
|
||||
|
||||
// Predeclare the domstylerule actor type
|
||||
types.addActorType("domstylerule");
|
||||
|
||||
/**
|
||||
* DOM Nodes returned by the style actor will be owned by the DOM walker
|
||||
* for the connection.
|
||||
*/
|
||||
types.addLifetime("walker", "walker");
|
||||
|
||||
/**
|
||||
* When asking for the styles applied to a node, we return a list of
|
||||
* appliedstyle json objects that lists the rules that apply to the node
|
||||
* and which element they were inherited from (if any).
|
||||
*
|
||||
* Note appliedstyle only sends the list of actorIDs and is not a valid return
|
||||
* value on its own. appliedstyle should be returned with the actual list of
|
||||
* StyleRuleActor and StyleSheetActor. See appliedStylesReturn.
|
||||
*/
|
||||
types.addDictType("appliedstyle", {
|
||||
rule: "domstylerule#actorid",
|
||||
inherited: "nullable:domnode#actorid",
|
||||
keyframes: "nullable:domstylerule#actorid"
|
||||
});
|
||||
|
||||
types.addDictType("matchedselector", {
|
||||
rule: "domstylerule#actorid",
|
||||
selector: "string",
|
||||
value: "string",
|
||||
status: "number"
|
||||
});
|
||||
|
||||
types.addDictType("appliedStylesReturn", {
|
||||
entries: "array:appliedstyle",
|
||||
rules: "array:domstylerule",
|
||||
sheets: "array:stylesheet"
|
||||
});
|
||||
|
||||
types.addDictType("modifiedStylesReturn", {
|
||||
isMatching: RetVal("boolean"),
|
||||
ruleProps: RetVal("nullable:appliedStylesReturn")
|
||||
});
|
||||
|
||||
types.addDictType("fontpreview", {
|
||||
data: "nullable:longstring",
|
||||
size: "json"
|
||||
});
|
||||
|
||||
types.addDictType("fontface", {
|
||||
name: "string",
|
||||
CSSFamilyName: "string",
|
||||
rule: "nullable:domstylerule",
|
||||
srcIndex: "number",
|
||||
URI: "string",
|
||||
format: "string",
|
||||
preview: "nullable:fontpreview",
|
||||
localName: "string",
|
||||
metadata: "string"
|
||||
});
|
||||
|
||||
/**
|
||||
* The PageStyle actor lets the client look at the styles on a page, as
|
||||
* they are applied to a given node.
|
||||
*/
|
||||
var PageStyleActor = protocol.ActorClass({
|
||||
typeName: "pagestyle",
|
||||
|
||||
events: {
|
||||
"stylesheet-updated": {
|
||||
type: "styleSheetUpdated",
|
||||
styleSheet: Arg(0, "stylesheet")
|
||||
}
|
||||
},
|
||||
|
||||
var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
|
||||
/**
|
||||
* Create a PageStyleActor.
|
||||
*
|
||||
@ -286,7 +213,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
getComputed: method(function(node, options) {
|
||||
getComputed: function(node, options) {
|
||||
let ret = Object.create(null);
|
||||
|
||||
this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA;
|
||||
@ -312,17 +239,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
}
|
||||
|
||||
return ret;
|
||||
}, {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
markMatched: Option(1, "boolean"),
|
||||
onlyMatched: Option(1, "boolean"),
|
||||
filter: Option(1, "string"),
|
||||
},
|
||||
response: {
|
||||
computed: RetVal("json")
|
||||
}
|
||||
}),
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all the fonts from a page.
|
||||
@ -335,7 +252,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
* @returns object
|
||||
* object with 'fontFaces', a list of fonts that apply to this node.
|
||||
*/
|
||||
getAllUsedFontFaces: method(function(options) {
|
||||
getAllUsedFontFaces: function(options) {
|
||||
let windows = this.inspector.tabActor.windows;
|
||||
let fontsList = [];
|
||||
for (let win of windows) {
|
||||
@ -343,17 +260,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
...this.getUsedFontFaces(win.document.body, options)];
|
||||
}
|
||||
return fontsList;
|
||||
}, {
|
||||
request: {
|
||||
includePreviews: Option(0, "boolean"),
|
||||
previewText: Option(0, "string"),
|
||||
previewFontSize: Option(0, "string"),
|
||||
previewFillStyle: Option(0, "string")
|
||||
},
|
||||
response: {
|
||||
fontFaces: RetVal("array:fontface")
|
||||
}
|
||||
}),
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the font faces used in an element.
|
||||
@ -368,7 +275,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
* @returns object
|
||||
* object with 'fontFaces', a list of fonts that apply to this node.
|
||||
*/
|
||||
getUsedFontFaces: method(function(node, options) {
|
||||
getUsedFontFaces: function(node, options) {
|
||||
// node.rawNode is defined for NodeActor objects
|
||||
let actualNode = node.rawNode || node;
|
||||
let contentDocument = actualNode.ownerDocument;
|
||||
@ -448,18 +355,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
});
|
||||
|
||||
return fontsArray;
|
||||
}, {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
includePreviews: Option(1, "boolean"),
|
||||
previewText: Option(1, "string"),
|
||||
previewFontSize: Option(1, "string"),
|
||||
previewFillStyle: Option(1, "string")
|
||||
},
|
||||
response: {
|
||||
fontFaces: RetVal("array:fontface")
|
||||
}
|
||||
}),
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a list of selectors that match a given property for a node.
|
||||
@ -497,7 +393,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
* sheets: [ <domsheet>, ... ]
|
||||
* }
|
||||
*/
|
||||
getMatchedSelectors: method(function(node, property, options) {
|
||||
getMatchedSelectors: function(node, property, options) {
|
||||
this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA;
|
||||
this.cssLogic.highlight(node.rawNode);
|
||||
|
||||
@ -530,18 +426,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
rules: [...rules],
|
||||
sheets: [...sheets]
|
||||
};
|
||||
}, {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
property: Arg(1, "string"),
|
||||
filter: Option(2, "string")
|
||||
},
|
||||
response: RetVal(types.addDictType("matchedselectorresponse", {
|
||||
rules: "array:domstylerule",
|
||||
sheets: "array:stylesheet",
|
||||
matched: "array:matchedselector"
|
||||
}))
|
||||
}),
|
||||
},
|
||||
|
||||
// Get a selector source for a CssSelectorInfo relative to a given
|
||||
// node.
|
||||
@ -571,7 +456,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
* `matchedSelectors`: Include an array of specific selectors that
|
||||
* caused this rule to match its node.
|
||||
*/
|
||||
getApplied: method(Task.async(function* (node, options) {
|
||||
getApplied: Task.async(function* (node, options) {
|
||||
if (!node) {
|
||||
return {entries: [], rules: [], sheets: []};
|
||||
}
|
||||
@ -587,14 +472,6 @@ var PageStyleActor = protocol.ActorClass({
|
||||
yield rule.getAuthoredCssText();
|
||||
}
|
||||
return result;
|
||||
}), {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
inherited: Option(1, "boolean"),
|
||||
matchedSelectors: Option(1, "boolean"),
|
||||
filter: Option(1, "string")
|
||||
},
|
||||
response: RetVal("appliedStylesReturn")
|
||||
}),
|
||||
|
||||
_hasInheritedProps: function(style) {
|
||||
@ -603,7 +480,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
});
|
||||
},
|
||||
|
||||
isPositionEditable: method(Task.async(function* (node) {
|
||||
isPositionEditable: Task.async(function* (node) {
|
||||
if (!node || node.rawNode.nodeType !== node.rawNode.ELEMENT_NODE) {
|
||||
return false;
|
||||
}
|
||||
@ -616,9 +493,6 @@ var PageStyleActor = protocol.ActorClass({
|
||||
props.has("right") ||
|
||||
props.has("left") ||
|
||||
props.has("bottom");
|
||||
}), {
|
||||
request: { node: Arg(0, "domnode")},
|
||||
response: { value: RetVal("boolean") }
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -900,7 +774,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
* all margins that are set to auto, e.g. {top: "auto", left: "auto"}.
|
||||
* @return {Object}
|
||||
*/
|
||||
getLayout: method(function(node, options) {
|
||||
getLayout: function(node, options) {
|
||||
this.cssLogic.highlight(node.rawNode);
|
||||
|
||||
let layout = {};
|
||||
@ -945,13 +819,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
}
|
||||
|
||||
return layout;
|
||||
}, {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
autoMargins: Option(1, "boolean")
|
||||
},
|
||||
response: RetVal("json")
|
||||
}),
|
||||
},
|
||||
|
||||
/**
|
||||
* Find 'auto' margin properties.
|
||||
@ -1020,8 +888,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
* CSSOM.
|
||||
* @returns {StyleRuleActor} the new rule
|
||||
*/
|
||||
addNewRule: method(Task.async(function* (node, pseudoClasses,
|
||||
editAuthored = false) {
|
||||
addNewRule: Task.async(function* (node, pseudoClasses, editAuthored = false) {
|
||||
let style = this.getStyleElement(node.rawNode.ownerDocument);
|
||||
let sheet = style.sheet;
|
||||
let cssRules = sheet.cssRules;
|
||||
@ -1052,82 +919,9 @@ var PageStyleActor = protocol.ActorClass({
|
||||
}
|
||||
|
||||
return this.getNewAppliedProps(node, sheet.cssRules.item(index));
|
||||
}), {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
pseudoClasses: Arg(1, "nullable:array:string"),
|
||||
editAuthored: Arg(2, "boolean")
|
||||
},
|
||||
response: RetVal("appliedStylesReturn")
|
||||
}),
|
||||
});
|
||||
exports.PageStyleActor = PageStyleActor;
|
||||
|
||||
/**
|
||||
* PageStyleFront, the front object for the PageStyleActor
|
||||
*/
|
||||
protocol.FrontClass(PageStyleActor, {
|
||||
initialize: function(conn, form, ctx, detail) {
|
||||
protocol.Front.prototype.initialize.call(this, conn, form, ctx, detail);
|
||||
this.inspector = this.parent();
|
||||
},
|
||||
|
||||
form: function(form, detail) {
|
||||
if (detail === "actorid") {
|
||||
this.actorID = form;
|
||||
return;
|
||||
}
|
||||
this._form = form;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
protocol.Front.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
get walker() {
|
||||
return this.inspector.walker;
|
||||
},
|
||||
|
||||
get supportsAuthoredStyles() {
|
||||
return this._form.traits && this._form.traits.authoredStyles;
|
||||
},
|
||||
|
||||
getMatchedSelectors: protocol.custom(function(node, property, options) {
|
||||
return this._getMatchedSelectors(node, property, options).then(ret => {
|
||||
return ret.matched;
|
||||
});
|
||||
}, {
|
||||
impl: "_getMatchedSelectors"
|
||||
}),
|
||||
|
||||
getApplied: protocol.custom(Task.async(function* (node, options = {}) {
|
||||
// If the getApplied method doesn't recreate the style cache itself, this
|
||||
// means a call to cssLogic.highlight is required before trying to access
|
||||
// the applied rules. Issue a request to getLayout if this is the case.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1103993#c16.
|
||||
if (!this._form.traits || !this._form.traits.getAppliedCreatesStyleCache) {
|
||||
yield this.getLayout(node);
|
||||
}
|
||||
let ret = yield this._getApplied(node, options);
|
||||
return ret.entries;
|
||||
}), {
|
||||
impl: "_getApplied"
|
||||
}),
|
||||
|
||||
addNewRule: protocol.custom(function(node, pseudoClasses) {
|
||||
let addPromise;
|
||||
if (this.supportsAuthoredStyles) {
|
||||
addPromise = this._addNewRule(node, pseudoClasses, true);
|
||||
} else {
|
||||
addPromise = this._addNewRule(node, pseudoClasses);
|
||||
}
|
||||
return addPromise.then(ret => {
|
||||
return ret.entries[0];
|
||||
});
|
||||
}, {
|
||||
impl: "_addNewRule"
|
||||
})
|
||||
});
|
||||
exports.PageStyleActor = PageStyleActor;
|
||||
|
||||
/**
|
||||
* An actor that represents a CSS style object on the protocol.
|
||||
@ -1137,17 +931,7 @@ protocol.FrontClass(PageStyleActor, {
|
||||
* (which have a CSSStyle but no CSSRule) we create a StyleRuleActor
|
||||
* with a special rule type (100).
|
||||
*/
|
||||
var StyleRuleActor = protocol.ActorClass({
|
||||
typeName: "domstylerule",
|
||||
|
||||
events: {
|
||||
"location-changed": {
|
||||
type: "locationChanged",
|
||||
line: Arg(0, "number"),
|
||||
column: Arg(1, "number")
|
||||
},
|
||||
},
|
||||
|
||||
var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
|
||||
initialize: function(pageStyle, item) {
|
||||
protocol.Actor.prototype.initialize.call(this, null);
|
||||
this.pageStyle = pageStyle;
|
||||
@ -1454,7 +1238,7 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
* @param {String} newText the new text of the rule
|
||||
* @returns the rule with updated properties
|
||||
*/
|
||||
setRuleText: method(Task.async(function* (newText) {
|
||||
setRuleText: Task.async(function* (newText) {
|
||||
if (!this.canSetRuleText ||
|
||||
(this.type !== Ci.nsIDOMCSSRule.STYLE_RULE &&
|
||||
this.type !== Ci.nsIDOMCSSRule.KEYFRAME_RULE)) {
|
||||
@ -1472,9 +1256,6 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
yield parentStyleSheet.update(cssText, false, UPDATE_PRESERVING_RULES);
|
||||
|
||||
return this;
|
||||
}), {
|
||||
request: { modification: Arg(0, "string") },
|
||||
response: { rule: RetVal("domstylerule") }
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -1493,7 +1274,7 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
*
|
||||
* @returns the rule with updated properties
|
||||
*/
|
||||
modifyProperties: method(function(modifications) {
|
||||
modifyProperties: function(modifications) {
|
||||
// Use a fresh element for each call to this function to prevent side
|
||||
// effects that pop up based on property values that were already set on the
|
||||
// element.
|
||||
@ -1524,10 +1305,7 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
}
|
||||
|
||||
return this;
|
||||
}, {
|
||||
request: { modifications: Arg(0, "array:json") },
|
||||
response: { rule: RetVal("domstylerule") }
|
||||
}),
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function for modifySelector and modifySelector2, inserts the new
|
||||
@ -1604,7 +1382,7 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
* Returns a boolean if the selector in the stylesheet was modified,
|
||||
* and false otherwise
|
||||
*/
|
||||
modifySelector: method(Task.async(function* (value) {
|
||||
modifySelector: Task.async(function* (value) {
|
||||
if (this.type === ELEMENT_STYLE) {
|
||||
return false;
|
||||
}
|
||||
@ -1627,9 +1405,6 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}), {
|
||||
request: { selector: Arg(0, "string") },
|
||||
response: { isModified: RetVal("boolean") },
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -1655,7 +1430,7 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
* new rule and a boolean indicating whether or not the new selector
|
||||
* matches the current selected element
|
||||
*/
|
||||
modifySelector2: method(function(node, value, editAuthored = false) {
|
||||
modifySelector2: function(node, value, editAuthored = false) {
|
||||
if (this.type === ELEMENT_STYLE ||
|
||||
this.rawRule.selectorText === value) {
|
||||
return { ruleProps: null, isMatching: true };
|
||||
@ -1692,337 +1467,7 @@ var StyleRuleActor = protocol.ActorClass({
|
||||
|
||||
return { ruleProps, isMatching };
|
||||
});
|
||||
}, {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
value: Arg(1, "string"),
|
||||
editAuthored: Arg(2, "boolean")
|
||||
},
|
||||
response: RetVal("modifiedStylesReturn")
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* StyleRuleFront, the front for the StyleRule actor.
|
||||
*/
|
||||
protocol.FrontClass(StyleRuleActor, {
|
||||
initialize: function(client, form, ctx, detail) {
|
||||
protocol.Front.prototype.initialize.call(this, client, form, ctx, detail);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
protocol.Front.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
form: function(form, detail) {
|
||||
if (detail === "actorid") {
|
||||
this.actorID = form;
|
||||
return;
|
||||
}
|
||||
this.actorID = form.actor;
|
||||
this._form = form;
|
||||
if (this._mediaText) {
|
||||
this._mediaText = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensure _form is updated when location-changed is emitted.
|
||||
*/
|
||||
_locationChangedPre: protocol.preEvent("location-changed", function(line,
|
||||
column) {
|
||||
this._clearOriginalLocation();
|
||||
this._form.line = line;
|
||||
this._form.column = column;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Return a new RuleModificationList or RuleRewriter for this node.
|
||||
* A RuleRewriter will be returned when the rule's canSetRuleText
|
||||
* trait is true; otherwise a RuleModificationList will be
|
||||
* returned.
|
||||
*/
|
||||
startModifyingProperties: function() {
|
||||
if (this.canSetRuleText) {
|
||||
return new RuleRewriter(this, this.authoredText);
|
||||
}
|
||||
return new RuleModificationList(this);
|
||||
},
|
||||
|
||||
get type() {
|
||||
return this._form.type;
|
||||
},
|
||||
get line() {
|
||||
return this._form.line || -1;
|
||||
},
|
||||
get column() {
|
||||
return this._form.column || -1;
|
||||
},
|
||||
get cssText() {
|
||||
return this._form.cssText;
|
||||
},
|
||||
get authoredText() {
|
||||
return this._form.authoredText || this._form.cssText;
|
||||
},
|
||||
get keyText() {
|
||||
return this._form.keyText;
|
||||
},
|
||||
get name() {
|
||||
return this._form.name;
|
||||
},
|
||||
get selectors() {
|
||||
return this._form.selectors;
|
||||
},
|
||||
get media() {
|
||||
return this._form.media;
|
||||
},
|
||||
get mediaText() {
|
||||
if (!this._form.media) {
|
||||
return null;
|
||||
}
|
||||
if (this._mediaText) {
|
||||
return this._mediaText;
|
||||
}
|
||||
this._mediaText = this.media.join(", ");
|
||||
return this._mediaText;
|
||||
},
|
||||
|
||||
get parentRule() {
|
||||
return this.conn.getActor(this._form.parentRule);
|
||||
},
|
||||
|
||||
get parentStyleSheet() {
|
||||
return this.conn.getActor(this._form.parentStyleSheet);
|
||||
},
|
||||
|
||||
get element() {
|
||||
return this.conn.getActor(this._form.element);
|
||||
},
|
||||
|
||||
get href() {
|
||||
if (this._form.href) {
|
||||
return this._form.href;
|
||||
}
|
||||
let sheet = this.parentStyleSheet;
|
||||
return sheet ? sheet.href : "";
|
||||
},
|
||||
|
||||
get nodeHref() {
|
||||
let sheet = this.parentStyleSheet;
|
||||
return sheet ? sheet.nodeHref : "";
|
||||
},
|
||||
|
||||
get supportsModifySelectorUnmatched() {
|
||||
return this._form.traits && this._form.traits.modifySelectorUnmatched;
|
||||
},
|
||||
|
||||
get canSetRuleText() {
|
||||
return this._form.traits && this._form.traits.canSetRuleText;
|
||||
},
|
||||
|
||||
get location() {
|
||||
return {
|
||||
source: this.parentStyleSheet,
|
||||
href: this.href,
|
||||
line: this.line,
|
||||
column: this.column
|
||||
};
|
||||
},
|
||||
|
||||
_clearOriginalLocation: function() {
|
||||
this._originalLocation = null;
|
||||
},
|
||||
|
||||
getOriginalLocation: function() {
|
||||
if (this._originalLocation) {
|
||||
return promise.resolve(this._originalLocation);
|
||||
}
|
||||
let parentSheet = this.parentStyleSheet;
|
||||
if (!parentSheet) {
|
||||
// This rule doesn't belong to a stylesheet so it is an inline style.
|
||||
// Inline styles do not have any mediaText so we can return early.
|
||||
return promise.resolve(this.location);
|
||||
}
|
||||
return parentSheet.getOriginalLocation(this.line, this.column)
|
||||
.then(({ fromSourceMap, source, line, column }) => {
|
||||
let location = {
|
||||
href: source,
|
||||
line: line,
|
||||
column: column,
|
||||
mediaText: this.mediaText
|
||||
};
|
||||
if (fromSourceMap === false) {
|
||||
location.source = this.parentStyleSheet;
|
||||
}
|
||||
if (!source) {
|
||||
location.href = this.href;
|
||||
}
|
||||
this._originalLocation = location;
|
||||
return location;
|
||||
});
|
||||
},
|
||||
|
||||
modifySelector: protocol.custom(Task.async(function* (node, value) {
|
||||
let response;
|
||||
if (this.supportsModifySelectorUnmatched) {
|
||||
// If the debugee supports adding unmatched rules (post FF41)
|
||||
if (this.canSetRuleText) {
|
||||
response = yield this.modifySelector2(node, value, true);
|
||||
} else {
|
||||
response = yield this.modifySelector2(node, value);
|
||||
}
|
||||
} else {
|
||||
response = yield this._modifySelector(value);
|
||||
}
|
||||
|
||||
if (response.ruleProps) {
|
||||
response.ruleProps = response.ruleProps.entries[0];
|
||||
}
|
||||
return response;
|
||||
}), {
|
||||
impl: "_modifySelector"
|
||||
}),
|
||||
|
||||
setRuleText: protocol.custom(function(newText) {
|
||||
this._form.authoredText = newText;
|
||||
return this._setRuleText(newText);
|
||||
}, {
|
||||
impl: "_setRuleText"
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* Convenience API for building a list of attribute modifications
|
||||
* for the `modifyProperties` request. A RuleModificationList holds a
|
||||
* list of modifications that will be applied to a StyleRuleActor.
|
||||
* The modifications are processed in the order in which they are
|
||||
* added to the RuleModificationList.
|
||||
*
|
||||
* Objects of this type expose the same API as @see RuleRewriter.
|
||||
* This lets the inspector use (mostly) the same code, regardless of
|
||||
* whether the server implements setRuleText.
|
||||
*/
|
||||
var RuleModificationList = Class({
|
||||
/**
|
||||
* Initialize a RuleModificationList.
|
||||
* @param {StyleRuleFront} rule the associated rule
|
||||
*/
|
||||
initialize: function(rule) {
|
||||
this.rule = rule;
|
||||
this.modifications = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply the modifications in this object to the associated rule.
|
||||
*
|
||||
* @return {Promise} A promise which will be resolved when the modifications
|
||||
* are complete; @see StyleRuleActor.modifyProperties.
|
||||
*/
|
||||
apply: function() {
|
||||
return this.rule.modifyProperties(this.modifications);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a "set" entry to the modification list.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name the property's name
|
||||
* @param {String} value the property's value
|
||||
* @param {String} priority the property's priority, either the empty
|
||||
* string or "important"
|
||||
*/
|
||||
setProperty: function(index, name, value, priority) {
|
||||
this.modifications.push({
|
||||
type: "set",
|
||||
name: name,
|
||||
value: value,
|
||||
priority: priority
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a "remove" entry to the modification list.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name the name of the property to remove
|
||||
*/
|
||||
removeProperty: function(index, name) {
|
||||
this.modifications.push({
|
||||
type: "remove",
|
||||
name: name
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Rename a property. This implementation acts like
|
||||
* |removeProperty|, because |setRuleText| is not available.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name current name of the property
|
||||
*
|
||||
* This parameter is also passed, but as it is not used in this
|
||||
* implementation, it is omitted. It is documented here as this
|
||||
* code also defined the interface implemented by @see RuleRewriter.
|
||||
* @param {String} newName new name of the property
|
||||
*/
|
||||
renameProperty: function(index, name) {
|
||||
this.removeProperty(index, name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable or disable a property. This implementation acts like
|
||||
* |removeProperty| when disabling, or a no-op when enabling,
|
||||
* because |setRuleText| is not available.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name current name of the property
|
||||
* @param {Boolean} isEnabled true if the property should be enabled;
|
||||
* false if it should be disabled
|
||||
*/
|
||||
setPropertyEnabled: function(index, name, isEnabled) {
|
||||
if (!isEnabled) {
|
||||
this.removeProperty(index, name);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new property. This implementation does nothing, because
|
||||
* |setRuleText| is not available.
|
||||
*
|
||||
* These parameter are passed, but as they are not used in this
|
||||
* implementation, they are omitted. They are documented here as
|
||||
* this code also defined the interface implemented by @see
|
||||
* RuleRewriter.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name name of the new property
|
||||
* @param {String} value value of the new property
|
||||
* @param {String} priority priority of the new property; either
|
||||
* the empty string or "important"
|
||||
*/
|
||||
createProperty: function() {
|
||||
// Nothing.
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -6,5 +6,6 @@
|
||||
|
||||
DevToolsModules(
|
||||
'storage.js',
|
||||
'styles.js',
|
||||
'stylesheets.js'
|
||||
)
|
||||
|
202
devtools/shared/specs/styles.js
Normal file
202
devtools/shared/specs/styles.js
Normal file
@ -0,0 +1,202 @@
|
||||
/* 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 {
|
||||
Arg,
|
||||
Option,
|
||||
RetVal,
|
||||
generateActorSpec,
|
||||
types
|
||||
} = require("devtools/shared/protocol.js");
|
||||
require("devtools/shared/specs/stylesheets.js");
|
||||
|
||||
// Predeclare the domnode actor type for use in requests.
|
||||
types.addActorType("domnode");
|
||||
|
||||
// Predeclare the domstylerule actor type
|
||||
types.addActorType("domstylerule");
|
||||
|
||||
/**
|
||||
* DOM Nodes returned by the style actor will be owned by the DOM walker
|
||||
* for the connection.
|
||||
*/
|
||||
types.addLifetime("walker", "walker");
|
||||
|
||||
/**
|
||||
* When asking for the styles applied to a node, we return a list of
|
||||
* appliedstyle json objects that lists the rules that apply to the node
|
||||
* and which element they were inherited from (if any).
|
||||
*
|
||||
* Note appliedstyle only sends the list of actorIDs and is not a valid return
|
||||
* value on its own. appliedstyle should be returned with the actual list of
|
||||
* StyleRuleActor and StyleSheetActor. See appliedStylesReturn.
|
||||
*/
|
||||
types.addDictType("appliedstyle", {
|
||||
rule: "domstylerule#actorid",
|
||||
inherited: "nullable:domnode#actorid",
|
||||
keyframes: "nullable:domstylerule#actorid"
|
||||
});
|
||||
|
||||
types.addDictType("matchedselector", {
|
||||
rule: "domstylerule#actorid",
|
||||
selector: "string",
|
||||
value: "string",
|
||||
status: "number"
|
||||
});
|
||||
|
||||
types.addDictType("appliedStylesReturn", {
|
||||
entries: "array:appliedstyle",
|
||||
rules: "array:domstylerule",
|
||||
sheets: "array:stylesheet"
|
||||
});
|
||||
|
||||
types.addDictType("modifiedStylesReturn", {
|
||||
isMatching: RetVal("boolean"),
|
||||
ruleProps: RetVal("nullable:appliedStylesReturn")
|
||||
});
|
||||
|
||||
types.addDictType("fontpreview", {
|
||||
data: "nullable:longstring",
|
||||
size: "json"
|
||||
});
|
||||
|
||||
types.addDictType("fontface", {
|
||||
name: "string",
|
||||
CSSFamilyName: "string",
|
||||
rule: "nullable:domstylerule",
|
||||
srcIndex: "number",
|
||||
URI: "string",
|
||||
format: "string",
|
||||
preview: "nullable:fontpreview",
|
||||
localName: "string",
|
||||
metadata: "string"
|
||||
});
|
||||
|
||||
const pageStyleSpec = generateActorSpec({
|
||||
typeName: "pagestyle",
|
||||
|
||||
events: {
|
||||
"stylesheet-updated": {
|
||||
type: "styleSheetUpdated",
|
||||
styleSheet: Arg(0, "stylesheet")
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getComputed: {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
markMatched: Option(1, "boolean"),
|
||||
onlyMatched: Option(1, "boolean"),
|
||||
filter: Option(1, "string"),
|
||||
},
|
||||
response: {
|
||||
computed: RetVal("json")
|
||||
}
|
||||
},
|
||||
getAllUsedFontFaces: {
|
||||
request: {
|
||||
includePreviews: Option(0, "boolean"),
|
||||
previewText: Option(0, "string"),
|
||||
previewFontSize: Option(0, "string"),
|
||||
previewFillStyle: Option(0, "string")
|
||||
},
|
||||
response: {
|
||||
fontFaces: RetVal("array:fontface")
|
||||
}
|
||||
},
|
||||
getUsedFontFaces: {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
includePreviews: Option(1, "boolean"),
|
||||
previewText: Option(1, "string"),
|
||||
previewFontSize: Option(1, "string"),
|
||||
previewFillStyle: Option(1, "string")
|
||||
},
|
||||
response: {
|
||||
fontFaces: RetVal("array:fontface")
|
||||
}
|
||||
},
|
||||
getMatchedSelectors: {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
property: Arg(1, "string"),
|
||||
filter: Option(2, "string")
|
||||
},
|
||||
response: RetVal(types.addDictType("matchedselectorresponse", {
|
||||
rules: "array:domstylerule",
|
||||
sheets: "array:stylesheet",
|
||||
matched: "array:matchedselector"
|
||||
}))
|
||||
},
|
||||
getApplied: {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
inherited: Option(1, "boolean"),
|
||||
matchedSelectors: Option(1, "boolean"),
|
||||
filter: Option(1, "string")
|
||||
},
|
||||
response: RetVal("appliedStylesReturn")
|
||||
},
|
||||
isPositionEditable: {
|
||||
request: { node: Arg(0, "domnode")},
|
||||
response: { value: RetVal("boolean") }
|
||||
},
|
||||
getLayout: {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
autoMargins: Option(1, "boolean")
|
||||
},
|
||||
response: RetVal("json")
|
||||
},
|
||||
addNewRule: {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
pseudoClasses: Arg(1, "nullable:array:string"),
|
||||
editAuthored: Arg(2, "boolean")
|
||||
},
|
||||
response: RetVal("appliedStylesReturn")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
exports.pageStyleSpec = pageStyleSpec;
|
||||
|
||||
const styleRuleSpec = generateActorSpec({
|
||||
typeName: "domstylerule",
|
||||
|
||||
events: {
|
||||
"location-changed": {
|
||||
type: "locationChanged",
|
||||
line: Arg(0, "number"),
|
||||
column: Arg(1, "number")
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
setRuleText: {
|
||||
request: { modification: Arg(0, "string") },
|
||||
response: { rule: RetVal("domstylerule") }
|
||||
},
|
||||
modifyProperties: {
|
||||
request: { modifications: Arg(0, "array:json") },
|
||||
response: { rule: RetVal("domstylerule") }
|
||||
},
|
||||
modifySelector: {
|
||||
request: { selector: Arg(0, "string") },
|
||||
response: { isModified: RetVal("boolean") },
|
||||
},
|
||||
modifySelector2: {
|
||||
request: {
|
||||
node: Arg(0, "domnode"),
|
||||
value: Arg(1, "string"),
|
||||
editAuthored: Arg(2, "boolean")
|
||||
},
|
||||
response: RetVal("modifiedStylesReturn")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
exports.styleRuleSpec = styleRuleSpec;
|
@ -8,7 +8,7 @@ const {
|
||||
RetVal,
|
||||
generateActorSpec,
|
||||
types
|
||||
} = require("devtools/shared/protocol.js");
|
||||
} = require("devtools/shared/protocol");
|
||||
|
||||
const originalSourceSpec = generateActorSpec({
|
||||
typeName: "originalsource",
|
||||
|
@ -216,7 +216,9 @@ HTMLTrackElement::LoadResource()
|
||||
static_cast<Element*>(this),
|
||||
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
|
||||
nsIContentPolicy::TYPE_INTERNAL_TRACK,
|
||||
loadGroup);
|
||||
loadGroup,
|
||||
nullptr, // aCallbacks
|
||||
nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI);
|
||||
|
||||
NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
|
||||
|
||||
|
@ -57,7 +57,14 @@
|
||||
android:windowSoftInputMode="stateUnspecified|adjustResize"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true"
|
||||
android:theme="@style/Gecko.App">
|
||||
android:theme="@style/Gecko.App" />
|
||||
|
||||
<!-- Bug 1256615 / Bug 1268455: We published an .App alias and we need to maintain it
|
||||
forever. If we don't, home screen shortcuts will disappear because the intent
|
||||
filter details change. -->
|
||||
<activity-alias android:name=".App"
|
||||
android:label="@MOZ_APP_DISPLAYNAME@"
|
||||
android:targetActivity="@MOZ_ANDROID_BROWSER_INTENT_CLASS@">
|
||||
|
||||
<!-- android:priority ranges between -1000 and 1000. We never want
|
||||
another activity to usurp the MAIN action, so we ratchet our
|
||||
@ -139,18 +146,6 @@
|
||||
<action android:name="org.mozilla.gecko.DEBUG" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Bug 1256615: We published a .App alias and we need to maintain it
|
||||
forever. If we don't, dock icons (e.g., Samsung Touchwiz icons)
|
||||
will disappear because the intent filter details change. -->
|
||||
<activity-alias android:name=".App"
|
||||
android:label="@MOZ_APP_DISPLAYNAME@"
|
||||
android:targetActivity="@MOZ_ANDROID_BROWSER_INTENT_CLASS@">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<service android:name="org.mozilla.gecko.GeckoService" />
|
||||
|
@ -43,6 +43,7 @@ import org.mozilla.gecko.home.HomeBanner;
|
||||
import org.mozilla.gecko.home.HomeConfig;
|
||||
import org.mozilla.gecko.home.HomeConfig.PanelType;
|
||||
import org.mozilla.gecko.home.HomeConfigPrefsBackend;
|
||||
import org.mozilla.gecko.home.HomeFragment;
|
||||
import org.mozilla.gecko.home.HomePager;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
@ -199,7 +200,6 @@ public class BrowserApp extends GeckoApp
|
||||
public static final String GUEST_BROWSING_ARG = "--guest";
|
||||
|
||||
// Intent String extras used to specify custom Switchboard configurations.
|
||||
private static final String INTENT_KEY_SWITCHBOARD_UUID = "switchboard-uuid";
|
||||
private static final String INTENT_KEY_SWITCHBOARD_HOST = "switchboard-host";
|
||||
|
||||
private static final String DEFAULT_SWITCHBOARD_HOST = "switchboard.services.mozilla.com";
|
||||
@ -679,6 +679,8 @@ public class BrowserApp extends GeckoApp
|
||||
"CharEncoding:State",
|
||||
"Download:AndroidDownloadManager",
|
||||
"Experiments:GetActive",
|
||||
"Experiments:SetOverride",
|
||||
"Experiments:ClearOverride",
|
||||
"Favicon:CacheLoad",
|
||||
"Feedback:MaybeLater",
|
||||
"Menu:Add",
|
||||
@ -793,14 +795,11 @@ public class BrowserApp extends GeckoApp
|
||||
return;
|
||||
}
|
||||
|
||||
final String switchboardUUID = ContextUtils.getStringExtra(intent, INTENT_KEY_SWITCHBOARD_UUID);
|
||||
SwitchBoard.setUUIDFromExtra(switchboardUUID);
|
||||
|
||||
// Loads the Switchboard config from the specified server URL. Eventually, we
|
||||
// should use the endpoint returned by the server URL, to support migrating
|
||||
// to a new endpoint. However, if we want to do that, we'll need to find a different
|
||||
// solution for dynamically changing the server URL from the intent.
|
||||
new AsyncConfigLoader(this, switchboardUUID, serverUrl).execute();
|
||||
new AsyncConfigLoader(this, serverUrl).execute();
|
||||
}
|
||||
|
||||
private void showUpdaterPermissionSnackbar() {
|
||||
@ -1386,6 +1385,8 @@ public class BrowserApp extends GeckoApp
|
||||
"CharEncoding:State",
|
||||
"Download:AndroidDownloadManager",
|
||||
"Experiments:GetActive",
|
||||
"Experiments:SetOverride",
|
||||
"Experiments:ClearOverride",
|
||||
"Favicon:CacheLoad",
|
||||
"Feedback:MaybeLater",
|
||||
"Menu:Add",
|
||||
@ -1661,6 +1662,10 @@ public class BrowserApp extends GeckoApp
|
||||
final List<String> experiments = SwitchBoard.getActiveExperiments(this);
|
||||
final JSONArray json = new JSONArray(experiments);
|
||||
callback.sendSuccess(json.toString());
|
||||
} else if ("Experiments:SetOverride".equals(event)) {
|
||||
Experiments.setOverride(getContext(), message.getString("name"), message.getBoolean("isEnabled"));
|
||||
} else if ("Experiments:ClearOverride".equals(event)) {
|
||||
Experiments.clearOverride(getContext(), message.getString("name"));
|
||||
} else if ("Favicon:CacheLoad".equals(event)) {
|
||||
final String url = message.getString("url");
|
||||
getFaviconFromCache(callback, url);
|
||||
@ -2305,7 +2310,7 @@ public class BrowserApp extends GeckoApp
|
||||
if (isUserSearchTerm && SwitchBoard.isInExperiment(getContext(), Experiments.SEARCH_TERM)) {
|
||||
showBrowserSearchAfterAnimation(animator);
|
||||
} else {
|
||||
showHomePagerWithAnimator(panelId, animator);
|
||||
showHomePagerWithAnimator(panelId, null, animator);
|
||||
}
|
||||
|
||||
animator.start();
|
||||
@ -2496,14 +2501,28 @@ public class BrowserApp extends GeckoApp
|
||||
return;
|
||||
}
|
||||
|
||||
// History will only store that we were visiting about:home, however the specific panel
|
||||
// isn't stored. (We are able to navigate directly to homepanels using an about:home?panel=...
|
||||
// URL, but the reverse doesn't apply: manually switching panels doesn't update the URL.)
|
||||
// Hence we need to restore the panel, in addition to panel state, here.
|
||||
if (isAboutHome(tab)) {
|
||||
String panelId = AboutPages.getPanelIdFromAboutHomeUrl(tab.getURL());
|
||||
Bundle panelRestoreData = null;
|
||||
if (panelId == null) {
|
||||
// No panel was specified in the URL. Try loading the most recent
|
||||
// home panel for this tab.
|
||||
// Note: this isn't necessarily correct. We don't update the URL when we switch tabs.
|
||||
// If a user explicitly navigated to about:reader?panel=FOO, and then switches
|
||||
// to panel BAR, the history URL still contains FOO, and we restore to FOO. In most
|
||||
// cases however we aren't supplying a panel ID in the URL so this code still works
|
||||
// for most cases.
|
||||
// We can't fix this directly since we can't ignore the panelId if we're explicitly
|
||||
// loading a specific panel, and we currently can't distinguish between loading
|
||||
// history, and loading new pages, see Bug 1268887
|
||||
panelId = tab.getMostRecentHomePanel();
|
||||
panelRestoreData = tab.getMostRecentHomePanelData();
|
||||
}
|
||||
showHomePager(panelId);
|
||||
showHomePager(panelId, panelRestoreData);
|
||||
|
||||
if (mDynamicToolbar.isEnabled()) {
|
||||
mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
|
||||
@ -2588,14 +2607,14 @@ public class BrowserApp extends GeckoApp
|
||||
mHomePagerContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void showHomePager(String panelId) {
|
||||
showHomePagerWithAnimator(panelId, null);
|
||||
private void showHomePager(String panelId, Bundle panelRestoreData) {
|
||||
showHomePagerWithAnimator(panelId, panelRestoreData, null);
|
||||
}
|
||||
|
||||
private void showHomePagerWithAnimator(String panelId, PropertyAnimator animator) {
|
||||
private void showHomePagerWithAnimator(String panelId, Bundle panelRestoreData, PropertyAnimator animator) {
|
||||
if (isHomePagerVisible()) {
|
||||
// Home pager already visible, make sure it shows the correct panel.
|
||||
mHomePager.showPanel(panelId);
|
||||
mHomePager.showPanel(panelId, panelRestoreData);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2628,6 +2647,17 @@ public class BrowserApp extends GeckoApp
|
||||
}
|
||||
});
|
||||
|
||||
// Set this listener to persist restore data (via the Tab) every time panel state changes.
|
||||
mHomePager.setPanelStateChangeListener(new HomeFragment.PanelStateChangeListener() {
|
||||
@Override
|
||||
public void onStateChanged(Bundle bundle) {
|
||||
final Tab currentTab = Tabs.getInstance().getSelectedTab();
|
||||
if (currentTab != null) {
|
||||
currentTab.setMostRecentHomePanelData(bundle);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Don't show the banner in guest mode.
|
||||
if (!Restrictions.isUserRestricted()) {
|
||||
final ViewStub homeBannerStub = (ViewStub) findViewById(R.id.home_banner_stub);
|
||||
@ -2648,7 +2678,9 @@ public class BrowserApp extends GeckoApp
|
||||
mHomePagerContainer.setVisibility(View.VISIBLE);
|
||||
mHomePager.load(getSupportLoaderManager(),
|
||||
getSupportFragmentManager(),
|
||||
panelId, animator);
|
||||
panelId,
|
||||
panelRestoreData,
|
||||
animator);
|
||||
|
||||
// Hide the web content so it cannot be focused by screen readers.
|
||||
hideWebContentOnPropertyAnimationEnd(animator);
|
||||
@ -2804,7 +2836,8 @@ public class BrowserApp extends GeckoApp
|
||||
|
||||
// To prevent overdraw, the HomePager is hidden when BrowserSearch is displayed:
|
||||
// reverse that.
|
||||
showHomePager(Tabs.getInstance().getSelectedTab().getMostRecentHomePanel());
|
||||
showHomePager(Tabs.getInstance().getSelectedTab().getMostRecentHomePanel(),
|
||||
Tabs.getInstance().getSelectedTab().getMostRecentHomePanelData());
|
||||
|
||||
mBrowserSearchContainer.setVisibility(View.INVISIBLE);
|
||||
|
||||
|
@ -263,6 +263,10 @@ public class GeckoNetworkManager extends BroadcastReceiver implements NativeEven
|
||||
* Update current network state and connection types.
|
||||
*/
|
||||
private void updateNetworkStateAndConnectionType() {
|
||||
if (applicationContext == null) {
|
||||
Log.i(LOGTAG, "applicationContext is null while trying to update network state");
|
||||
return;
|
||||
}
|
||||
final ConnectivityManager connectivityManager = (ConnectivityManager) applicationContext.getSystemService(
|
||||
Context.CONNECTIVITY_SERVICE);
|
||||
// Type/status getters below all have a defined behaviour for when connectivityManager == null
|
||||
|
@ -36,6 +36,7 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
@ -88,6 +89,13 @@ public class Tab {
|
||||
private String mMostRecentHomePanel;
|
||||
private boolean mShouldShowToolbarWithoutAnimationOnFirstSelection;
|
||||
|
||||
/*
|
||||
* Bundle containing restore data for the panel referenced in mMostRecentHomePanel. This can be
|
||||
* e.g. the most recent folder for the bookmarks panel, or any other state that should be
|
||||
* persisted. This is then used e.g. when returning to homepanels via history.
|
||||
*/
|
||||
private Bundle mMostRecentHomePanelData;
|
||||
|
||||
private int mHistoryIndex;
|
||||
private int mHistorySize;
|
||||
private boolean mCanDoBack;
|
||||
@ -219,8 +227,17 @@ public class Tab {
|
||||
return mMostRecentHomePanel;
|
||||
}
|
||||
|
||||
public Bundle getMostRecentHomePanelData() {
|
||||
return mMostRecentHomePanelData;
|
||||
}
|
||||
|
||||
public void setMostRecentHomePanel(String panelId) {
|
||||
mMostRecentHomePanel = panelId;
|
||||
mMostRecentHomePanelData = null;
|
||||
}
|
||||
|
||||
public void setMostRecentHomePanelData(Bundle data) {
|
||||
mMostRecentHomePanelData = data;
|
||||
}
|
||||
|
||||
public Bitmap getThumbnailBitmap(int width, int height) {
|
||||
|
@ -127,10 +127,14 @@ public class Distribution {
|
||||
* {@link org.mozilla.gecko.distribution.Distribution#exists()} will return
|
||||
* false. In the other two callbacks, it will return true.
|
||||
*/
|
||||
@WorkerThread
|
||||
public interface ReadyCallback {
|
||||
@WorkerThread
|
||||
void distributionNotFound();
|
||||
|
||||
@WorkerThread
|
||||
void distributionFound(Distribution distribution);
|
||||
|
||||
@WorkerThread
|
||||
void distributionArrivedLate(Distribution distribution);
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.IOUtils;
|
||||
import org.mozilla.gecko.util.ProxySelector;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
@ -24,7 +25,8 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
public abstract class BaseAction {
|
||||
private static final String LOGTAG = "GeckoDLCBaseAction";
|
||||
@ -145,11 +147,10 @@ public abstract class BaseAction {
|
||||
|
||||
protected HttpURLConnection buildHttpURLConnection(String url)
|
||||
throws UnrecoverableDownloadContentException, IOException {
|
||||
// TODO: Implement proxy support (Bug 1209496)
|
||||
try {
|
||||
System.setProperty("http.keepAlive", "true");
|
||||
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(new URI(url));
|
||||
connection.setRequestProperty("User-Agent", HardwareUtils.isTablet() ?
|
||||
AppConstants.USER_AGENT_FENNEC_TABLET :
|
||||
AppConstants.USER_AGENT_FENNEC_MOBILE);
|
||||
@ -158,6 +159,8 @@ public abstract class BaseAction {
|
||||
return connection;
|
||||
} catch (MalformedURLException e) {
|
||||
throw new UnrecoverableDownloadContentException(e);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new UnrecoverableDownloadContentException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -445,11 +445,23 @@ public class LoadFaviconTask {
|
||||
|
||||
while (loadedBitmaps.getBitmaps().hasNext()) {
|
||||
final Bitmap b = loadedBitmaps.getBitmaps().next();
|
||||
iconMap.put(b.getWidth(), b);
|
||||
sizes.add(b.getWidth());
|
||||
|
||||
// It's possible to receive null, most likely due to OOM or a zero-sized image,
|
||||
// from BitmapUtils.decodeByteArray(byte[], int, int, BitmapFactory.Options)
|
||||
if (b != null) {
|
||||
iconMap.put(b.getWidth(), b);
|
||||
sizes.add(b.getWidth());
|
||||
}
|
||||
}
|
||||
|
||||
int bestSize = Favicons.selectBestSizeFromList(sizes, targetWidthAndHeight);
|
||||
|
||||
if (bestSize == -1) {
|
||||
// No icons found: this could occur if we weren't able to process any of the
|
||||
// supplied icons.
|
||||
return null;
|
||||
}
|
||||
|
||||
return iconMap.get(bestSize);
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ class BookmarksListAdapter extends MultiTypeCursorAdapter {
|
||||
|
||||
// mParentStack holds folder info instances (id + title) that allow
|
||||
// us to navigate back up the folder hierarchy.
|
||||
private final LinkedList<FolderInfo> mParentStack;
|
||||
private LinkedList<FolderInfo> mParentStack;
|
||||
|
||||
// Refresh folder listener.
|
||||
private OnRefreshFolderListener mListener;
|
||||
@ -137,6 +137,11 @@ class BookmarksListAdapter extends MultiTypeCursorAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
public void restoreData(List<FolderInfo> parentStack) {
|
||||
mParentStack = new LinkedList<FolderInfo>(parentStack);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public List<FolderInfo> getParentStack() {
|
||||
return Collections.unmodifiableList(mParentStack);
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
@ -22,6 +24,8 @@ import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
@ -61,6 +65,20 @@ public class BookmarksPanel extends HomeFragment {
|
||||
// Callback for cursor loaders.
|
||||
private CursorLoaderCallbacks mLoaderCallbacks;
|
||||
|
||||
@Override
|
||||
public void restoreData(@NonNull Bundle data) {
|
||||
final ArrayList<FolderInfo> stack = data.getParcelableArrayList("parentStack");
|
||||
if (stack == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mListAdapter == null) {
|
||||
mSavedParentStack = new LinkedList<FolderInfo>(stack);
|
||||
} else {
|
||||
mListAdapter.restoreData(stack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.home_bookmarks_panel, container, false);
|
||||
@ -151,7 +169,16 @@ public class BookmarksPanel extends HomeFragment {
|
||||
|
||||
@Override
|
||||
protected void load() {
|
||||
getLoaderManager().initLoader(LOADER_ID_BOOKMARKS_LIST, null, mLoaderCallbacks);
|
||||
final Bundle bundle;
|
||||
if (mSavedParentStack != null && mSavedParentStack.size() > 1) {
|
||||
bundle = new Bundle();
|
||||
bundle.putParcelable(BOOKMARKS_FOLDER_INFO, mSavedParentStack.get(0));
|
||||
bundle.putParcelable(BOOKMARKS_REFRESH_TYPE, RefreshType.CHILD);
|
||||
} else {
|
||||
bundle = null;
|
||||
}
|
||||
|
||||
getLoaderManager().initLoader(LOADER_ID_BOOKMARKS_LIST, bundle, mLoaderCallbacks);
|
||||
}
|
||||
|
||||
private void updateUiFromCursor(Cursor c) {
|
||||
@ -232,6 +259,19 @@ public class BookmarksPanel extends HomeFragment {
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
||||
BookmarksLoader bl = (BookmarksLoader) loader;
|
||||
mListAdapter.swapCursor(c, bl.getFolderInfo(), bl.getRefreshType());
|
||||
|
||||
if (mPanelStateChangeListener != null) {
|
||||
final List<FolderInfo> parentStack = mListAdapter.getParentStack();
|
||||
final Bundle bundle = new Bundle();
|
||||
|
||||
// Bundle likes to store ArrayLists or Arrays, but we've got a generic List (which
|
||||
// is actually an unmodifiable wrapper around a LinkedList). We'll need to do a
|
||||
// LinkedList conversion at the other end, when saving we need to use this awkward
|
||||
// syntax to create an Array.
|
||||
bundle.putParcelableArrayList("parentStack", new ArrayList<FolderInfo>(parentStack));
|
||||
|
||||
mPanelStateChangeListener.onStateChanged(bundle);
|
||||
}
|
||||
updateUiFromCursor(c);
|
||||
}
|
||||
|
||||
|
@ -18,29 +18,42 @@ import android.view.ViewGroup;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HomeAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
private final Context mContext;
|
||||
private final ArrayList<PanelInfo> mPanelInfos;
|
||||
private final HashMap<String, Fragment> mPanels;
|
||||
private final Map<String, HomeFragment> mPanels;
|
||||
private final Map<String, Bundle> mRestoreBundles;
|
||||
|
||||
private boolean mCanLoadHint;
|
||||
|
||||
private OnAddPanelListener mAddPanelListener;
|
||||
|
||||
private HomeFragment.PanelStateChangeListener mPanelStateChangeListener = null;
|
||||
|
||||
public interface OnAddPanelListener {
|
||||
void onAddPanel(String title);
|
||||
}
|
||||
|
||||
public void setPanelStateChangeListener(HomeFragment.PanelStateChangeListener listener) {
|
||||
mPanelStateChangeListener = listener;
|
||||
|
||||
for (Fragment fragment : mPanels.values()) {
|
||||
((HomeFragment) fragment).setPanelStateChangeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public HomeAdapter(Context context, FragmentManager fm) {
|
||||
super(fm);
|
||||
|
||||
mContext = context;
|
||||
mCanLoadHint = HomeFragment.DEFAULT_CAN_LOAD_HINT;
|
||||
|
||||
mPanelInfos = new ArrayList<PanelInfo>();
|
||||
mPanels = new HashMap<String, Fragment>();
|
||||
mPanelInfos = new ArrayList<>();
|
||||
mPanels = new HashMap<>();
|
||||
mRestoreBundles = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -66,16 +79,40 @@ public class HomeAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
@Override
|
||||
public Object instantiateItem(ViewGroup container, int position) {
|
||||
Fragment fragment = (Fragment) super.instantiateItem(container, position);
|
||||
mPanels.put(mPanelInfos.get(position).getId(), fragment);
|
||||
final HomeFragment fragment = (HomeFragment) super.instantiateItem(container, position);
|
||||
fragment.setPanelStateChangeListener(mPanelStateChangeListener);
|
||||
|
||||
final String id = mPanelInfos.get(position).getId();
|
||||
mPanels.put(id, fragment);
|
||||
|
||||
if (mRestoreBundles.containsKey(id)) {
|
||||
fragment.restoreData(mRestoreBundles.get(id));
|
||||
mRestoreBundles.remove(id);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public void setRestoreData(int position, Bundle data) {
|
||||
final String id = mPanelInfos.get(position).getId();
|
||||
final HomeFragment fragment = mPanels.get(id);
|
||||
|
||||
// We have no guarantees as to whether our desired fragment is instantiated yet: therefore
|
||||
// we might need to either pass data to the fragment, or store it for later.
|
||||
if (fragment != null) {
|
||||
fragment.restoreData(data);
|
||||
} else {
|
||||
mRestoreBundles.put(id, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
final String id = mPanelInfos.get(position).getId();
|
||||
|
||||
super.destroyItem(container, position, object);
|
||||
mPanels.remove(mPanelInfos.get(position).getId());
|
||||
mPanels.remove(id);
|
||||
}
|
||||
|
||||
public void setOnAddPanelListener(OnAddPanelListener listener) {
|
||||
|
@ -77,6 +77,30 @@ public abstract class HomeFragment extends Fragment {
|
||||
// Helper for opening a tab in the background.
|
||||
private OnUrlOpenInBackgroundListener mUrlOpenInBackgroundListener;
|
||||
|
||||
protected PanelStateChangeListener mPanelStateChangeListener = null;
|
||||
|
||||
/**
|
||||
* Listener to notify when a home panels' state has changed in a way that needs to be stored
|
||||
* for history/restoration. E.g. when a folder is opened/closed in bookmarks.
|
||||
*/
|
||||
public interface PanelStateChangeListener {
|
||||
|
||||
/**
|
||||
* @param bundle Data that should be persisted, and passed to this panel if restored at a later
|
||||
* stage.
|
||||
*/
|
||||
void onStateChanged(Bundle bundle);
|
||||
}
|
||||
|
||||
public void restoreData(Bundle data) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public void setPanelStateChangeListener(
|
||||
PanelStateChangeListener mPanelStateChangeListener) {
|
||||
this.mPanelStateChangeListener = mPanelStateChangeListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
@ -49,6 +49,7 @@ public class HomePager extends ViewPager {
|
||||
private final ConfigLoaderCallbacks mConfigLoaderCallbacks;
|
||||
|
||||
private String mInitialPanelId;
|
||||
private Bundle mRestoreData;
|
||||
|
||||
// Cached original ViewPager background.
|
||||
private final Drawable mOriginalBackground;
|
||||
@ -63,6 +64,8 @@ public class HomePager extends ViewPager {
|
||||
// Listens for when the current panel changes.
|
||||
private OnPanelChangeListener mPanelChangedListener;
|
||||
|
||||
private HomeFragment.PanelStateChangeListener mPanelStateChangeListener;
|
||||
|
||||
// This is mostly used by UI tests to easily fetch
|
||||
// specific list views at runtime.
|
||||
public static final String LIST_TAG_HISTORY = "history";
|
||||
@ -197,11 +200,12 @@ public class HomePager extends ViewPager {
|
||||
*
|
||||
* @param fm FragmentManager for the adapter
|
||||
*/
|
||||
public void load(LoaderManager lm, FragmentManager fm, String panelId, PropertyAnimator animator) {
|
||||
public void load(LoaderManager lm, FragmentManager fm, String panelId, Bundle restoreData, PropertyAnimator animator) {
|
||||
mLoadState = LoadState.LOADING;
|
||||
|
||||
mVisible = true;
|
||||
mInitialPanelId = panelId;
|
||||
mRestoreData = restoreData;
|
||||
|
||||
// Update the home banner message each time the HomePager is loaded.
|
||||
if (mHomeBanner != null) {
|
||||
@ -213,6 +217,7 @@ public class HomePager extends ViewPager {
|
||||
|
||||
final HomeAdapter adapter = new HomeAdapter(mContext, fm);
|
||||
adapter.setOnAddPanelListener(mAddPanelListener);
|
||||
adapter.setPanelStateChangeListener(mPanelStateChangeListener);
|
||||
adapter.setCanLoadHint(true);
|
||||
setAdapter(adapter);
|
||||
|
||||
@ -281,6 +286,10 @@ public class HomePager extends ViewPager {
|
||||
}
|
||||
}
|
||||
|
||||
private void restorePanelData(int item, Bundle data) {
|
||||
((HomeAdapter) getAdapter()).setRestoreData(item, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a home panel. If the given panelId is null,
|
||||
* the default panel will be shown. No action will be taken if:
|
||||
@ -292,7 +301,7 @@ public class HomePager extends ViewPager {
|
||||
*
|
||||
* @param panelId of the home panel to be shown.
|
||||
*/
|
||||
public void showPanel(String panelId) {
|
||||
public void showPanel(String panelId, Bundle restoreData) {
|
||||
if (!mVisible) {
|
||||
return;
|
||||
}
|
||||
@ -300,6 +309,7 @@ public class HomePager extends ViewPager {
|
||||
switch (mLoadState) {
|
||||
case LOADING:
|
||||
mInitialPanelId = panelId;
|
||||
mRestoreData = restoreData;
|
||||
break;
|
||||
|
||||
case LOADED:
|
||||
@ -310,6 +320,9 @@ public class HomePager extends ViewPager {
|
||||
|
||||
if (position > -1) {
|
||||
setCurrentItem(position);
|
||||
if (restoreData != null) {
|
||||
restorePanelData(position, restoreData);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -420,6 +433,10 @@ public class HomePager extends ViewPager {
|
||||
final int itemPosition = (mInitialPanelId == null) ? -1 : adapter.getItemPosition(mInitialPanelId);
|
||||
if (itemPosition > -1) {
|
||||
setCurrentItem(itemPosition, false);
|
||||
if (mRestoreData != null) {
|
||||
restorePanelData(itemPosition, mRestoreData);
|
||||
mRestoreData = null; // Release data since it's no longer needed
|
||||
}
|
||||
mInitialPanelId = null;
|
||||
} else {
|
||||
setCurrentItem(mDefaultPageIndex, false);
|
||||
@ -441,6 +458,15 @@ public class HomePager extends ViewPager {
|
||||
mPanelChangedListener = listener;
|
||||
}
|
||||
|
||||
public void setPanelStateChangeListener(HomeFragment.PanelStateChangeListener listener) {
|
||||
mPanelStateChangeListener = listener;
|
||||
|
||||
HomeAdapter adapter = (HomeAdapter) getAdapter();
|
||||
if (adapter != null) {
|
||||
adapter.setPanelStateChangeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify listeners of newly selected panel.
|
||||
*
|
||||
|
@ -9,8 +9,11 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
import ch.boye.httpclientandroidlib.HttpHeaders;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
|
||||
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
@ -19,12 +22,14 @@ import org.mozilla.gecko.sync.net.BaseResource;
|
||||
import org.mozilla.gecko.sync.net.BaseResourceDelegate;
|
||||
import org.mozilla.gecko.sync.net.Resource;
|
||||
import org.mozilla.gecko.telemetry.stores.TelemetryPingStore;
|
||||
import org.mozilla.gecko.util.DateUtil;
|
||||
import org.mozilla.gecko.util.NetworkUtils;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -300,6 +305,12 @@ public class TelemetryUploadService extends IntentService {
|
||||
private boolean hadConnectionError() {
|
||||
return hadConnectionError;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeaders(final HttpRequestBase request, final DefaultHttpClient client) {
|
||||
super.addHeaders(request, client);
|
||||
request.addHeader(HttpHeaders.DATE, DateUtil.getDateInHTTPFormat(Calendar.getInstance().getTime()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,9 +20,13 @@ import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.search.SearchEngine;
|
||||
import org.mozilla.gecko.telemetry.TelemetryConstants;
|
||||
import org.mozilla.gecko.telemetry.TelemetryPing;
|
||||
import org.mozilla.gecko.util.DateUtil;
|
||||
import org.mozilla.gecko.util.Experiments;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -35,7 +39,7 @@ import java.util.concurrent.TimeUnit;
|
||||
public class TelemetryCorePingBuilder extends TelemetryPingBuilder {
|
||||
|
||||
private static final String NAME = "core";
|
||||
private static final int VERSION_VALUE = 4; // For version history, see toolkit/components/telemetry/docs/core-ping.rst
|
||||
private static final int VERSION_VALUE = 5; // For version history, see toolkit/components/telemetry/docs/core-ping.rst
|
||||
private static final String OS_VALUE = "Android";
|
||||
|
||||
private static final String ARCHITECTURE = "arch";
|
||||
@ -47,8 +51,10 @@ public class TelemetryCorePingBuilder extends TelemetryPingBuilder {
|
||||
private static final String LOCALE = "locale";
|
||||
private static final String OS_ATTR = "os";
|
||||
private static final String OS_VERSION = "osversion";
|
||||
private static final String PING_CREATION_DATE = "created";
|
||||
private static final String PROFILE_CREATION_DATE = "profileDate";
|
||||
public static final String SEQ = "seq";
|
||||
private static final String TIMEZONE_OFFSET = "tz";
|
||||
private static final String VERSION_ATTR = "v";
|
||||
|
||||
public TelemetryCorePingBuilder(final Context context, final int sequenceNumber) {
|
||||
@ -67,10 +73,15 @@ public class TelemetryCorePingBuilder extends TelemetryPingBuilder {
|
||||
final String deviceDescriptor =
|
||||
StringUtils.safeSubstring(Build.MANUFACTURER, 0, 12) + '-' + StringUtils.safeSubstring(Build.MODEL, 0, 19);
|
||||
|
||||
final Calendar nowCalendar = Calendar.getInstance();
|
||||
final DateFormat pingCreationDateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
|
||||
payload.put(ARCHITECTURE, AppConstants.ANDROID_CPU_ARCH);
|
||||
payload.put(DEVICE, deviceDescriptor);
|
||||
payload.put(LOCALE, Locales.getLanguageTag(Locale.getDefault()));
|
||||
payload.put(OS_VERSION, Integer.toString(Build.VERSION.SDK_INT)); // A String for cross-platform reasons.
|
||||
payload.put(PING_CREATION_DATE, pingCreationDateFormat.format(nowCalendar.getTime()));
|
||||
payload.put(TIMEZONE_OFFSET, DateUtil.getTimezoneOffsetInMinutesForGivenDate(nowCalendar));
|
||||
payload.putArray(EXPERIMENTS, Experiments.getActiveExperiments(context));
|
||||
}
|
||||
|
||||
@ -89,8 +100,10 @@ public class TelemetryCorePingBuilder extends TelemetryPingBuilder {
|
||||
LOCALE,
|
||||
OS_ATTR,
|
||||
OS_VERSION,
|
||||
PING_CREATION_DATE,
|
||||
PROFILE_CREATION_DATE,
|
||||
SEQ,
|
||||
TIMEZONE_OFFSET,
|
||||
VERSION_ATTR,
|
||||
};
|
||||
}
|
||||
|
@ -265,6 +265,7 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean isHttpOrHttps = StringUtils.isHttpOrHttps(url);
|
||||
final String baseDomain = tab.getBaseDomain();
|
||||
|
||||
String strippedURL = stripAboutReaderURL(url);
|
||||
@ -282,12 +283,15 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout {
|
||||
if (siteIdentity.hasOwner()) {
|
||||
// Show Owner of EV certificate as title
|
||||
updateTitleFromSiteIdentity(siteIdentity);
|
||||
} else if (!HardwareUtils.isTablet() && !TextUtils.isEmpty(baseDomain)) {
|
||||
} else if (isHttpOrHttps && !HardwareUtils.isTablet() && !TextUtils.isEmpty(baseDomain)) {
|
||||
// Show just the base domain as title
|
||||
setTitle(baseDomain);
|
||||
} else {
|
||||
// Display full URL as title
|
||||
} else if (isHttpOrHttps) {
|
||||
// Display full URL with base domain highlighted as title
|
||||
updateAndColorTitleFromFullURL(strippedURL, baseDomain, tab.isPrivate());
|
||||
} else {
|
||||
// Not http(s): Just show the full URL as title
|
||||
setTitle(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
package org.mozilla.gecko.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Utilities to help with manipulating Java's dates and calendars.
|
||||
*/
|
||||
public class DateUtil {
|
||||
private DateUtil() {}
|
||||
|
||||
/**
|
||||
* @param date the date to convert to HTTP format
|
||||
* @return the date as specified in rfc 1123, e.g. "Tue, 01 Feb 2011 14:00:00 GMT"
|
||||
*/
|
||||
public static String getDateInHTTPFormat(@NonNull final Date date) {
|
||||
final DateFormat df = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.US);
|
||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
return df.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timezone offset for the current date in minutes. See
|
||||
* {@link #getTimezoneOffsetInMinutesForGivenDate(Calendar)} for more details.
|
||||
*/
|
||||
public static int getTimezoneOffsetInMinutes(@NonNull final TimeZone timezone) {
|
||||
return getTimezoneOffsetInMinutesForGivenDate(Calendar.getInstance(timezone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time zone offset for the given date in minutes. The date makes a difference due to daylight
|
||||
* savings time in some regions. We return minutes because we can accurately represent time zones that are
|
||||
* offset by non-integer hour values, e.g. parts of New Zealand at UTC+12:45.
|
||||
*
|
||||
* @param calendar A calendar with the appropriate time zone & date already set.
|
||||
*/
|
||||
public static int getTimezoneOffsetInMinutesForGivenDate(@NonNull final Calendar calendar) {
|
||||
// via Date.getTimezoneOffset deprecated docs (note: it had incorrect order of operations).
|
||||
// Also, we cast to int because we should never overflow here - the max should be GMT+14 = 840.
|
||||
return (int) TimeUnit.MILLISECONDS.toMinutes(calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET));
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@ import android.content.Context;
|
||||
import android.util.Log;
|
||||
import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.keepsafe.switchboard.Preferences;
|
||||
import com.keepsafe.switchboard.SwitchBoard;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
|
||||
@ -114,4 +116,28 @@ public class Experiments {
|
||||
|
||||
return experiments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an override to force an experiment to be enabled or disabled. This value
|
||||
* will be read and used before reading the switchboard server configuration.
|
||||
*
|
||||
* @param c Context
|
||||
* @param experimentName Experiment name
|
||||
* @param isEnabled Whether or not the experiment should be enabled
|
||||
*/
|
||||
public static void setOverride(Context c, String experimentName, boolean isEnabled) {
|
||||
Log.d(LOGTAG, "setOverride: " + experimentName + " = " + isEnabled);
|
||||
Preferences.setOverrideValue(c, experimentName, isEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the override value for an experiment.
|
||||
*
|
||||
* @param c Context
|
||||
* @param experimentName Experiment name
|
||||
*/
|
||||
public static void clearOverride(Context c, String experimentName) {
|
||||
Log.d(LOGTAG, "clearOverride: " + experimentName);
|
||||
Preferences.clearOverrideValue(c, experimentName);
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +92,14 @@ public class StringUtils {
|
||||
return url.substring(start, end);
|
||||
}
|
||||
|
||||
public static boolean isHttpOrHttps(String url) {
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return url.startsWith("http://") || url.startsWith("https://");
|
||||
}
|
||||
|
||||
public static String stripCommonSubdomains(String host) {
|
||||
if (host == null) {
|
||||
return host;
|
||||
|
@ -16,9 +16,6 @@
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.graphics.Rect;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
@ -29,6 +26,13 @@ import android.widget.AbsListView;
|
||||
import android.widget.AbsListView.RecyclerListener;
|
||||
import android.widget.ListView;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.ViewPropertyAnimator;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
/**
|
||||
* This code is based off of Jake Wharton's NOA port (https://github.com/JakeWharton/SwipeToDismissNOA)
|
||||
* of Roman Nurik's SwipeToDismiss library. It has been modified for better support with async
|
||||
@ -70,8 +74,6 @@ import android.widget.ListView;
|
||||
* @see SwipeDismissTouchListener
|
||||
*/
|
||||
public class SwipeDismissListViewTouchListener implements View.OnTouchListener {
|
||||
private static final int TAG_ORIGINAL_HEIGHT = SwipeDismissListViewTouchListener.class.hashCode();
|
||||
|
||||
// Cached ViewConfiguration and system-wide constant values
|
||||
private final int mSlop;
|
||||
private final int mMinFlingVelocity;
|
||||
@ -165,7 +167,7 @@ public class SwipeDismissListViewTouchListener implements View.OnTouchListener {
|
||||
return new AbsListView.RecyclerListener() {
|
||||
@Override
|
||||
public void onMovedToScrapHeap(View view) {
|
||||
final Object tag = view.getTag(TAG_ORIGINAL_HEIGHT);
|
||||
final Object tag = view.getTag(R.id.original_height);
|
||||
|
||||
// To reset the view to the correct height after its animation, the view's height
|
||||
// is stored in its tag. Reset the view here.
|
||||
@ -175,7 +177,7 @@ public class SwipeDismissListViewTouchListener implements View.OnTouchListener {
|
||||
final ViewGroup.LayoutParams lp = view.getLayoutParams();
|
||||
lp.height = (int) tag;
|
||||
view.setLayoutParams(lp);
|
||||
view.setTag(TAG_ORIGINAL_HEIGHT, null);
|
||||
view.setTag(R.id.original_height, null);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -334,7 +336,7 @@ public class SwipeDismissListViewTouchListener implements View.OnTouchListener {
|
||||
// height in the view's tag to flag it for the recycler. This is racy since the user
|
||||
// could scroll the dismissed view off the screen, then back on the screen, before
|
||||
// it's removed from the adapter, causing the dismissed view to briefly reappear.
|
||||
dismissView.setTag(TAG_ORIGINAL_HEIGHT, originalHeight);
|
||||
dismissView.setTag(R.id.original_height, originalHeight);
|
||||
|
||||
mCallback.onDismiss(mListView, dismissPosition);
|
||||
mDismissing = false;
|
||||
|
@ -97,6 +97,7 @@ gujar.sources += ['java/org/mozilla/gecko/' + x for x in [
|
||||
'util/BundleEventListener.java',
|
||||
'util/Clipboard.java',
|
||||
'util/ContextUtils.java',
|
||||
'util/DateUtil.java',
|
||||
'util/DrawableUtil.java',
|
||||
'util/EventCallback.java',
|
||||
'util/FileUtils.java',
|
||||
|
@ -8,6 +8,7 @@
|
||||
<item type="id" name="tabQueueNotification"/>
|
||||
<item type="id" name="tabQueueSettingsNotification" />
|
||||
<item type="id" name="guestNotification"/>
|
||||
<item type="id" name="original_height"/>
|
||||
<item type="id" name="menu_items"/>
|
||||
<item type="id" name="menu_margin"/>
|
||||
<item type="id" name="recycler_view_click_support" />
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user