Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-05-10 12:14:54 +02:00
commit 4a4aa387d6
128 changed files with 2876 additions and 824 deletions

View File

@ -76,7 +76,6 @@ browser/locales/**
browser/extensions/loop/**
# devtools/ exclusions
devtools/*.js
devtools/client/canvasdebugger/**
devtools/client/commandline/**
devtools/client/debugger/**

View File

@ -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&#38;ref=firefox_about&#38;utm_campaign=firefox_about&#38;tm_source=firefox&#38;tm_medium=referral&#38;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>

View File

@ -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>

View File

@ -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();

View File

@ -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]

View File

@ -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);
}

View File

@ -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);
});

View File

@ -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>

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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");
}

View File

@ -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 {

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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
View File

@ -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", {});

View File

@ -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");

View File

@ -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

View File

@ -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");

View File

@ -6,6 +6,7 @@
"use strict";
const EventEmitter = require("devtools/shared/event-emitter");
const {
createNode,
findOptimalTimeInterval,

View File

@ -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");
/**

View File

@ -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");

View File

@ -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

View File

@ -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");

View File

@ -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");

View File

@ -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);
});
}

View File

@ -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");
/**

View File

@ -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;

View File

@ -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";

View File

@ -6,5 +6,6 @@
DevToolsModules(
'storage.js',
'styles.js',
'stylesheets.js'
)

View 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.
},
});

View File

@ -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.

View File

@ -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";

View File

@ -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.

View File

@ -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",

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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.

View File

@ -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)

View File

@ -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;

View File

@ -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");

View File

@ -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"]

View File

@ -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");

View File

@ -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;

View File

@ -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) {
}
}
}

View File

@ -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");

View File

@ -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]

View 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");
});

View 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;
}

View 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;
}

View 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");
});

View 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");
});

View File

@ -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;

View 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");
}

View 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;
},
};

View File

@ -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

View File

@ -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",

View File

@ -20,6 +20,7 @@ DevToolsModules(
'FlameGraph.js',
'Graphs.js',
'GraphsWorker.js',
'HTMLTooltip.js',
'LineGraphWidget.js',
'MdnDocsWidget.js',
'MountainGraphWidget.js',

View File

@ -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 {

View 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>

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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";

View File

@ -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 {

View File

@ -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");

View File

@ -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;

View File

@ -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);

View File

@ -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");

View File

@ -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;

View File

@ -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();

View File

@ -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.
},
}
});
/**

View File

@ -6,5 +6,6 @@
DevToolsModules(
'storage.js',
'styles.js',
'stylesheets.js'
)

View 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;

View File

@ -8,7 +8,7 @@ const {
RetVal,
generateActorSpec,
types
} = require("devtools/shared/protocol.js");
} = require("devtools/shared/protocol");
const originalSourceSpec = generateActorSpec({
typeName: "originalsource",

View File

@ -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));

View File

@ -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" />

View File

@ -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);

View File

@ -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

View File

@ -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) {

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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) {

View File

@ -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);

View File

@ -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.
*

View File

@ -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()));
}
}
/**

View File

@ -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,
};
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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',

View File

@ -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