Merge mozilla-central to b2g-inbound
5
CLOBBER
@ -22,4 +22,7 @@
|
|||||||
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
||||||
# don't change CLOBBER for WebIDL changes any more.
|
# don't change CLOBBER for WebIDL changes any more.
|
||||||
|
|
||||||
Bug 1039866: Removing the metro browser
|
Bug 1038068: Check add-on signatures and refuse to install unsigned or broken add-ons
|
||||||
|
|
||||||
|
Not sure why this needs a clobber but tests perma-failed when they don't on
|
||||||
|
try (2).
|
||||||
|
@ -743,7 +743,6 @@
|
|||||||
#endif
|
#endif
|
||||||
@RESPATH@/@PREF_DIR@/channel-prefs.js
|
@RESPATH@/@PREF_DIR@/channel-prefs.js
|
||||||
@RESPATH@/greprefs.js
|
@RESPATH@/greprefs.js
|
||||||
@RESPATH@/defaults/autoconfig/platform.js
|
|
||||||
@RESPATH@/defaults/autoconfig/prefcalls.js
|
@RESPATH@/defaults/autoconfig/prefcalls.js
|
||||||
@RESPATH@/defaults/profile/prefs.js
|
@RESPATH@/defaults/profile/prefs.js
|
||||||
|
|
||||||
|
@ -69,6 +69,9 @@ pref("extensions.hotfix.certs.1.sha1Fingerprint", "91:53:98:0C:C1:86:DF:47:8F:35
|
|||||||
// See the SCOPE constants in AddonManager.jsm for values to use here.
|
// See the SCOPE constants in AddonManager.jsm for values to use here.
|
||||||
pref("extensions.autoDisableScopes", 15);
|
pref("extensions.autoDisableScopes", 15);
|
||||||
|
|
||||||
|
// Don't require signed add-ons by default
|
||||||
|
pref("xpinstall.signatures.required", false);
|
||||||
|
|
||||||
// Dictionary download preference
|
// Dictionary download preference
|
||||||
pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/firefox/dictionaries/");
|
pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/firefox/dictionaries/");
|
||||||
|
|
||||||
@ -1425,6 +1428,8 @@ pref("devtools.performance.enabled", true);
|
|||||||
pref("devtools.performance.memory.sample-probability", "0.05");
|
pref("devtools.performance.memory.sample-probability", "0.05");
|
||||||
pref("devtools.performance.memory.max-log-length", 2147483647); // Math.pow(2,31) - 1
|
pref("devtools.performance.memory.max-log-length", 2147483647); // Math.pow(2,31) - 1
|
||||||
pref("devtools.performance.timeline.hidden-markers", "[]");
|
pref("devtools.performance.timeline.hidden-markers", "[]");
|
||||||
|
pref("devtools.performance.profiler.buffer-size", 10000000);
|
||||||
|
pref("devtools.performance.profiler.sample-frequency-khz", 1);
|
||||||
pref("devtools.performance.ui.invert-call-tree", true);
|
pref("devtools.performance.ui.invert-call-tree", true);
|
||||||
pref("devtools.performance.ui.invert-flame-graph", false);
|
pref("devtools.performance.ui.invert-flame-graph", false);
|
||||||
pref("devtools.performance.ui.flatten-tree-recursion", true);
|
pref("devtools.performance.ui.flatten-tree-recursion", true);
|
||||||
@ -1530,6 +1535,9 @@ pref("devtools.webconsole.filter.info", true);
|
|||||||
pref("devtools.webconsole.filter.log", true);
|
pref("devtools.webconsole.filter.log", true);
|
||||||
pref("devtools.webconsole.filter.secerror", true);
|
pref("devtools.webconsole.filter.secerror", true);
|
||||||
pref("devtools.webconsole.filter.secwarn", true);
|
pref("devtools.webconsole.filter.secwarn", true);
|
||||||
|
pref("devtools.webconsole.filter.serviceworkers", false);
|
||||||
|
pref("devtools.webconsole.filter.sharedworkers", false);
|
||||||
|
pref("devtools.webconsole.filter.windowlessworkers", false);
|
||||||
|
|
||||||
// Remember the Browser Console filters
|
// Remember the Browser Console filters
|
||||||
pref("devtools.browserconsole.filter.network", true);
|
pref("devtools.browserconsole.filter.network", true);
|
||||||
@ -1548,6 +1556,9 @@ pref("devtools.browserconsole.filter.info", true);
|
|||||||
pref("devtools.browserconsole.filter.log", true);
|
pref("devtools.browserconsole.filter.log", true);
|
||||||
pref("devtools.browserconsole.filter.secerror", true);
|
pref("devtools.browserconsole.filter.secerror", true);
|
||||||
pref("devtools.browserconsole.filter.secwarn", true);
|
pref("devtools.browserconsole.filter.secwarn", true);
|
||||||
|
pref("devtools.browserconsole.filter.serviceworkers", true);
|
||||||
|
pref("devtools.browserconsole.filter.sharedworkers", true);
|
||||||
|
pref("devtools.browserconsole.filter.windowlessworkers", true);
|
||||||
|
|
||||||
// Text size in the Web Console. Use 0 for the system default size.
|
// Text size in the Web Console. Use 0 for the system default size.
|
||||||
pref("devtools.webconsole.fontSize", 0);
|
pref("devtools.webconsole.fontSize", 0);
|
||||||
@ -1891,3 +1902,8 @@ pref("reader.parse-node-limit", 0);
|
|||||||
#ifdef NIGHTLY_BUILD
|
#ifdef NIGHTLY_BUILD
|
||||||
pref("dom.serviceWorkers.enabled", true);
|
pref("dom.serviceWorkers.enabled", true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
pref("browser.pocket.enabled", false);
|
||||||
|
pref("browser.pocket.removedByUser", false);
|
||||||
|
pref("browser.pocket.useLocaleList", true);
|
||||||
|
pref("browser.pocket.enabledLocales", "en-US");
|
||||||
|
@ -114,5 +114,6 @@
|
|||||||
<stringbundleset id="stringbundleset">
|
<stringbundleset id="stringbundleset">
|
||||||
<stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
|
<stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
|
||||||
<stringbundle id="bundle_browser_region" src="chrome://browser-region/locale/region.properties"/>
|
<stringbundle id="bundle_browser_region" src="chrome://browser-region/locale/region.properties"/>
|
||||||
|
<stringbundle id="bundle_pocket" src="chrome://browser/content/browser-pocket.properties"/>
|
||||||
</stringbundleset>
|
</stringbundleset>
|
||||||
</overlay>
|
</overlay>
|
||||||
|
22
browser/base/content/browser-pocket.properties
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# 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/.
|
||||||
|
|
||||||
|
# This is a temporary file and not meant for localization; later versions
|
||||||
|
# of Firefox include these strings in browser.properties
|
||||||
|
|
||||||
|
pocket-button.label = Pocket
|
||||||
|
pocket-button.tooltiptext = Send this page to Pocket
|
||||||
|
pocket-header = Pocket
|
||||||
|
pocket-login-required-tagline = Catch the best content you find online with Pocket.
|
||||||
|
pocket-signup-with-fxa = Sign up with Firefox
|
||||||
|
pocket-signup-with-email = Sign up with email
|
||||||
|
pocket-account-question = Already have an account?
|
||||||
|
pocket-login-now = Log in now
|
||||||
|
pocket-page-saved-header = Page Saved
|
||||||
|
pocket-open-pocket = Open Pocket
|
||||||
|
pocket-remove-page = Remove Page
|
||||||
|
pocket-page-tags-field = Add Tags
|
||||||
|
pocket-page-tags-add = Save
|
||||||
|
pocket-page-suggested-tags-header = Suggested Tags
|
||||||
|
pocket-signup-or = Or
|
@ -782,9 +782,12 @@
|
|||||||
// failed URI (particularly for SSL errors). However, don't clear the value
|
// failed URI (particularly for SSL errors). However, don't clear the value
|
||||||
// if the error page's URI is about:blank, because that causes complete
|
// if the error page's URI is about:blank, because that causes complete
|
||||||
// loss of urlbar contents for invalid URI errors (see bug 867957).
|
// loss of urlbar contents for invalid URI errors (see bug 867957).
|
||||||
|
// Another reason to clear the userTypedValue is if this was an anchor
|
||||||
|
// navigation.
|
||||||
if (this.mBrowser.userTypedClear > 0 ||
|
if (this.mBrowser.userTypedClear > 0 ||
|
||||||
((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
|
((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
|
||||||
aLocation.spec != "about:blank"))
|
aLocation.spec != "about:blank") ||
|
||||||
|
aFlags && Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
|
||||||
this.mBrowser.userTypedValue = null;
|
this.mBrowser.userTypedValue = null;
|
||||||
|
|
||||||
if (this.mTabBrowser.isFindBarInitialized(this.mTab)) {
|
if (this.mTabBrowser.isFindBarInitialized(this.mTab)) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/* Tests for correct behaviour of getEffectiveHost on identity handler */
|
/* Tests for correct behaviour of getEffectiveHost on identity handler */
|
||||||
function test() {
|
function test() {
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
requestLongerTimeout(2);
|
||||||
|
|
||||||
ok(gIdentityHandler, "gIdentityHandler should exist");
|
ok(gIdentityHandler, "gIdentityHandler should exist");
|
||||||
|
|
||||||
|
@ -75,6 +75,7 @@ browser.jar:
|
|||||||
* content/browser/browser.css (content/browser.css)
|
* content/browser/browser.css (content/browser.css)
|
||||||
* content/browser/browser.js (content/browser.js)
|
* content/browser/browser.js (content/browser.js)
|
||||||
* content/browser/browser.xul (content/browser.xul)
|
* content/browser/browser.xul (content/browser.xul)
|
||||||
|
content/browser/browser-pocket.properties (content/browser-pocket.properties)
|
||||||
* content/browser/browser-tabPreviews.xml (content/browser-tabPreviews.xml)
|
* content/browser/browser-tabPreviews.xml (content/browser-tabPreviews.xml)
|
||||||
* content/browser/chatWindow.xul (content/chatWindow.xul)
|
* content/browser/chatWindow.xul (content/chatWindow.xul)
|
||||||
content/browser/tab-content.js (content/tab-content.js)
|
content/browser/tab-content.js (content/tab-content.js)
|
||||||
|
@ -33,6 +33,10 @@ XPCOMUtils.defineLazyGetter(this, "BrandBundle", function() {
|
|||||||
const kBrandBundle = "chrome://branding/locale/brand.properties";
|
const kBrandBundle = "chrome://branding/locale/brand.properties";
|
||||||
return Services.strings.createBundle(kBrandBundle);
|
return Services.strings.createBundle(kBrandBundle);
|
||||||
});
|
});
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "PocketBundle", function() {
|
||||||
|
const kPocketBundle = "chrome://browser/content/browser-pocket.properties";
|
||||||
|
return Services.strings.createBundle(kPocketBundle);
|
||||||
|
});
|
||||||
|
|
||||||
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||||
const kPrefCustomizationDebug = "browser.uiCustomization.debug";
|
const kPrefCustomizationDebug = "browser.uiCustomization.debug";
|
||||||
@ -1057,6 +1061,133 @@ if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Services.prefs.getBoolPref("browser.pocket.enabled")) {
|
||||||
|
let isEnabledForLocale = true;
|
||||||
|
if (Services.prefs.getBoolPref("browser.pocket.useLocaleList")) {
|
||||||
|
let chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"]
|
||||||
|
.getService(Ci.nsIXULChromeRegistry);
|
||||||
|
let browserLocale = chromeRegistry.getSelectedLocale("browser");
|
||||||
|
let enabledLocales = [];
|
||||||
|
try {
|
||||||
|
enabledLocales = Services.prefs.getCharPref("browser.pocket.enabledLocales").split(' ');
|
||||||
|
} catch (ex) {
|
||||||
|
Cu.reportError(ex);
|
||||||
|
}
|
||||||
|
isEnabledForLocale = enabledLocales.indexOf(browserLocale) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEnabledForLocale) {
|
||||||
|
let pocketButton = {
|
||||||
|
id: "pocket-button",
|
||||||
|
type: "view",
|
||||||
|
viewId: "PanelUI-pocketView",
|
||||||
|
label: PocketBundle.GetStringFromName("pocket-button.label"),
|
||||||
|
tooltiptext: PocketBundle.GetStringFromName("pocket-button.tooltiptext"),
|
||||||
|
onCreated(node) {
|
||||||
|
let doc = node.ownerDocument;
|
||||||
|
let elementsNeedingStrings = [
|
||||||
|
"pocket-header",
|
||||||
|
"pocket-login-required-tagline",
|
||||||
|
"pocket-signup-with-fxa",
|
||||||
|
"pocket-signup-with-email",
|
||||||
|
"pocket-account-question",
|
||||||
|
"pocket-login-now",
|
||||||
|
"pocket-page-saved-header",
|
||||||
|
"pocket-open-pocket",
|
||||||
|
"pocket-remove-page",
|
||||||
|
"pocket-page-tags-field",
|
||||||
|
"pocket-page-tags-add",
|
||||||
|
"pocket-page-suggested-tags-header",
|
||||||
|
"pocket-signup-or",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let elementId of elementsNeedingStrings) {
|
||||||
|
let el = doc.getElementById(elementId);
|
||||||
|
let string = PocketBundle.GetStringFromName(elementId);
|
||||||
|
|
||||||
|
switch (el.localName) {
|
||||||
|
case "button":
|
||||||
|
el.label = string;
|
||||||
|
break;
|
||||||
|
case "textbox":
|
||||||
|
el.setAttribute("placeholder", string);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
el.textContent = string;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let addTagsField = doc.getElementById("pocket-page-tags-field");
|
||||||
|
let addTagsButton = doc.getElementById("pocket-page-tags-add");
|
||||||
|
addTagsField.addEventListener("input", this);
|
||||||
|
addTagsButton.addEventListener("command", this);
|
||||||
|
},
|
||||||
|
onViewShowing(event) {
|
||||||
|
let doc = event.target.ownerDocument;
|
||||||
|
|
||||||
|
let loginView = doc.getElementById("pocket-login-required");
|
||||||
|
let pageSavedView = doc.getElementById("pocket-page-saved");
|
||||||
|
let showPageSaved = Math.random() < 0.5;
|
||||||
|
loginView.hidden = !showPageSaved;
|
||||||
|
pageSavedView.hidden = showPageSaved;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleEvent: function(event) {
|
||||||
|
let doc = event.target.ownerDocument;
|
||||||
|
let field = doc.getElementById("pocket-page-tags-field");
|
||||||
|
let button = doc.getElementById("pocket-page-tags-add");
|
||||||
|
switch (event.type) {
|
||||||
|
case "input":
|
||||||
|
button.disabled = !field.value.trim();
|
||||||
|
break;
|
||||||
|
case "command":
|
||||||
|
//XXXjaws Send tag to the Pocket server
|
||||||
|
field.value = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
USER_REMOVED_PREF: "browser.pocket.removedByUser",
|
||||||
|
onWidgetAdded(aWidgetId, aArea, aPosition) {
|
||||||
|
if (aWidgetId != this.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placement = CustomizableUI.getPlacementOfWidget(this.id);
|
||||||
|
let widgetInUI = placement && placement.area;
|
||||||
|
Services.prefs.setBoolPref(this.USER_REMOVED_PREF, !widgetInUI);
|
||||||
|
},
|
||||||
|
onWidgetRemoved(aWidgetId, aArea) {
|
||||||
|
if (aWidgetId != this.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placement = CustomizableUI.getPlacementOfWidget(this.id);
|
||||||
|
let widgetInUI = placement && placement.area;
|
||||||
|
Services.prefs.setBoolPref(this.USER_REMOVED_PREF, !widgetInUI);
|
||||||
|
},
|
||||||
|
onWidgetReset(aNode, aContainer) {
|
||||||
|
if (aNode.id != this.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Services.prefs.setBoolPref(this.USER_REMOVED_PREF, !aContainer);
|
||||||
|
},
|
||||||
|
onWidgetUndoMove(aNode, aContainer) {
|
||||||
|
if (aNode.id != this.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Services.prefs.setBoolPref(this.USER_REMOVED_PREF, !aContainer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CustomizableWidgets.push(pocketButton);
|
||||||
|
CustomizableUI.addListener(pocketButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef E10S_TESTING_ONLY
|
#ifdef E10S_TESTING_ONLY
|
||||||
/**
|
/**
|
||||||
* The e10s button's purpose is to lower the barrier of entry
|
* The e10s button's purpose is to lower the barrier of entry
|
||||||
|
@ -229,6 +229,45 @@
|
|||||||
</vbox>
|
</vbox>
|
||||||
</panelview>
|
</panelview>
|
||||||
|
|
||||||
|
<panelview id="PanelUI-pocketView" flex="1">
|
||||||
|
<vbox class="panel-subview-body">
|
||||||
|
<label id="pocket-header"/>
|
||||||
|
<vbox id="pocket-login-required" hidden="true">
|
||||||
|
<label id="pocket-login-required-tagline" class="pocket-subheader"/>
|
||||||
|
<description id="pocket-fxa-options">
|
||||||
|
<button id="pocket-signup-with-fxa" class="pocket-button"/>
|
||||||
|
<label id="pocket-signup-or"/>
|
||||||
|
<button id="pocket-signup-with-email" class="pocket-button"/>
|
||||||
|
</description>
|
||||||
|
<label id="pocket-account-question"/>
|
||||||
|
<label id="pocket-login-now" class="text-link"/>
|
||||||
|
</vbox>
|
||||||
|
<vbox id="pocket-page-saved" hidden="true">
|
||||||
|
<label id="pocket-page-saved-header" class="pocket-header"/>
|
||||||
|
<hbox id="pocket-page-saved-next-steps" pack="center">
|
||||||
|
<label id="pocket-open-pocket" class="text-link"/>
|
||||||
|
<label id="pocket-remove-page" class="text-link"/>
|
||||||
|
</hbox>
|
||||||
|
<hbox id="pocket-separator">
|
||||||
|
<box class="pocket-separator-colorstop"/>
|
||||||
|
<box class="pocket-separator-colorstop"/>
|
||||||
|
<box class="pocket-separator-colorstop"/>
|
||||||
|
<box class="pocket-separator-colorstop"/>
|
||||||
|
</hbox>
|
||||||
|
<vbox id="pocket-page-tags">
|
||||||
|
<hbox id="pocket-page-tags-form">
|
||||||
|
<textbox id="pocket-page-tags-field"
|
||||||
|
flex="1"/>
|
||||||
|
<button id="pocket-page-tags-add" disabled="true"/>
|
||||||
|
</hbox>
|
||||||
|
<label id="pocket-page-suggested-tags-header"/>
|
||||||
|
<description id="pocket-page-suggested-tags"/>
|
||||||
|
</vbox>
|
||||||
|
</vbox>
|
||||||
|
</vbox>
|
||||||
|
</panelview>
|
||||||
|
|
||||||
|
|
||||||
</panelmultiview>
|
</panelmultiview>
|
||||||
<!-- These menupopups are located here to prevent flickering,
|
<!-- These menupopups are located here to prevent flickering,
|
||||||
see bug 492960 comment 20. -->
|
see bug 492960 comment 20. -->
|
||||||
|
@ -69,6 +69,14 @@ class LoopTestServers:
|
|||||||
p = processhandler.ProcessHandler(CONTENT_SERVER_COMMAND,
|
p = processhandler.ProcessHandler(CONTENT_SERVER_COMMAND,
|
||||||
env=CONTENT_SERVER_ENV)
|
env=CONTENT_SERVER_ENV)
|
||||||
p.run()
|
p.run()
|
||||||
|
|
||||||
|
# Give the content server time to start.
|
||||||
|
import time
|
||||||
|
output = p.output
|
||||||
|
while not output:
|
||||||
|
time.sleep(1)
|
||||||
|
output = p.output
|
||||||
|
|
||||||
return p
|
return p
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
|
@ -102,6 +102,8 @@ class Test1BrowserCall(MarionetteTestCase):
|
|||||||
media_container = self.wait_for_element_displayed(By.CLASS_NAME, "media")
|
media_container = self.wait_for_element_displayed(By.CLASS_NAME, "media")
|
||||||
self.assertEqual(media_container.tag_name, "div", "expect a video container")
|
self.assertEqual(media_container.tag_name, "div", "expect a video container")
|
||||||
|
|
||||||
|
self.check_video(".local .OT_publisher .OT_widget-container");
|
||||||
|
|
||||||
def local_get_and_verify_room_url(self):
|
def local_get_and_verify_room_url(self):
|
||||||
self.switch_to_chatbox()
|
self.switch_to_chatbox()
|
||||||
button = self.wait_for_element_displayed(By.CLASS_NAME, "btn-copy")
|
button = self.wait_for_element_displayed(By.CLASS_NAME, "btn-copy")
|
||||||
@ -163,6 +165,10 @@ class Test1BrowserCall(MarionetteTestCase):
|
|||||||
feedback_form = self.wait_for_element_displayed(By.CLASS_NAME, "faces")
|
feedback_form = self.wait_for_element_displayed(By.CLASS_NAME, "faces")
|
||||||
self.assertEqual(feedback_form.tag_name, "div", "expect feedback form")
|
self.assertEqual(feedback_form.tag_name, "div", "expect feedback form")
|
||||||
|
|
||||||
|
self.switch_to_chatbox()
|
||||||
|
# check that the local view reverts to the preview mode
|
||||||
|
self.wait_for_element_displayed(By.CLASS_NAME, "room-invitation-content")
|
||||||
|
|
||||||
def local_get_chatbox_window_expr(self, expr):
|
def local_get_chatbox_window_expr(self, expr):
|
||||||
"""
|
"""
|
||||||
:expr: a sub-expression which must begin with a property of the
|
:expr: a sub-expression which must begin with a property of the
|
||||||
|
@ -13,6 +13,7 @@ DIRS += [
|
|||||||
'loop',
|
'loop',
|
||||||
'migration',
|
'migration',
|
||||||
'places',
|
'places',
|
||||||
|
'pocket',
|
||||||
'preferences',
|
'preferences',
|
||||||
'privatebrowsing',
|
'privatebrowsing',
|
||||||
'readinglist',
|
'readinglist',
|
||||||
|
BIN
browser/components/pocket/img/signup_logo.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
browser/components/pocket/img/signup_logo@2x.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
browser/components/pocket/img/signup_or.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
browser/components/pocket/img/signup_or@2x.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
9
browser/components/pocket/jar.mn
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 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/.
|
||||||
|
|
||||||
|
browser.jar:
|
||||||
|
content/browser/pocket/img/signup_logo.png (img/signup_logo.png)
|
||||||
|
content/browser/pocket/img/signup_logo@2x.png (img/signup_logo@2x.png)
|
||||||
|
content/browser/pocket/img/signup_or.png (img/signup_or.png)
|
||||||
|
content/browser/pocket/img/signup_or@2x.png (img/signup_or@2x.png)
|
5
browser/components/pocket/moz.build
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# 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/.
|
||||||
|
|
||||||
|
JAR_MANIFESTS += ['jar.mn']
|
@ -97,6 +97,8 @@ skip-if = e10s
|
|||||||
[browser_sessionStorage.js]
|
[browser_sessionStorage.js]
|
||||||
[browser_swapDocShells.js]
|
[browser_swapDocShells.js]
|
||||||
skip-if = e10s # See bug 918634
|
skip-if = e10s # See bug 918634
|
||||||
|
[browser_switch_remoteness.js]
|
||||||
|
run-if = e10s
|
||||||
[browser_telemetry.js]
|
[browser_telemetry.js]
|
||||||
[browser_upgrade_backup.js]
|
[browser_upgrade_backup.js]
|
||||||
[browser_windowRestore_perwindowpb.js]
|
[browser_windowRestore_perwindowpb.js]
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const URL = "http://example.com/browser_switch_remoteness_";
|
||||||
|
|
||||||
|
function countHistoryEntries(browser) {
|
||||||
|
return ContentTask.spawn(browser, null, function* () {
|
||||||
|
let Ci = Components.interfaces;
|
||||||
|
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||||
|
let history = webNavigation.sessionHistory.QueryInterface(Ci.nsISHistoryInternal);
|
||||||
|
return history && history.count;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(function* () {
|
||||||
|
// Add a new tab.
|
||||||
|
let tab = gBrowser.addTab("about:blank");
|
||||||
|
let browser = tab.linkedBrowser;
|
||||||
|
yield promiseBrowserLoaded(browser);
|
||||||
|
ok(browser.isRemoteBrowser, "browser is remote");
|
||||||
|
|
||||||
|
// Get the maximum number of preceding entries to save.
|
||||||
|
const MAX_BACK = Services.prefs.getIntPref("browser.sessionstore.max_serialize_back");
|
||||||
|
ok(MAX_BACK > -1, "check that the default has a value that caps data");
|
||||||
|
|
||||||
|
// Load more pages than we would save to disk on a clean shutdown.
|
||||||
|
for (let i = 0; i < MAX_BACK + 2; i++) {
|
||||||
|
browser.loadURI(URL + i);
|
||||||
|
yield promiseBrowserLoaded(browser);
|
||||||
|
ok(browser.isRemoteBrowser, "browser is still remote");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check we have the right number of shistory entries.
|
||||||
|
let count = yield countHistoryEntries(browser);
|
||||||
|
is(count, MAX_BACK + 2, "correct number of shistory entries");
|
||||||
|
|
||||||
|
// Load a non-remote page.
|
||||||
|
browser.loadURI("about:robots");
|
||||||
|
yield promiseTabRestored(tab);
|
||||||
|
ok(!browser.isRemoteBrowser, "browser is not remote anymore");
|
||||||
|
|
||||||
|
// Check that we didn't lose any shistory entries.
|
||||||
|
count = yield countHistoryEntries(browser);
|
||||||
|
is(count, MAX_BACK + 3, "correct number of shistory entries");
|
||||||
|
|
||||||
|
// Cleanup.
|
||||||
|
gBrowser.removeTab(tab);
|
||||||
|
});
|
@ -384,7 +384,7 @@ let CallsListView = Heritage.extend(WidgetMethods, {
|
|||||||
// If clicking on the location, jump to the Debugger.
|
// If clicking on the location, jump to the Debugger.
|
||||||
if (e.target.classList.contains("call-item-location")) {
|
if (e.target.classList.contains("call-item-location")) {
|
||||||
let { file, line } = callItem.attachment.actor;
|
let { file, line } = callItem.attachment.actor;
|
||||||
viewSourceInDebugger(file, line);
|
this._viewSourceInDebugger(file, line);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Otherwise hide the call stack.
|
// Otherwise hide the call stack.
|
||||||
@ -456,7 +456,7 @@ let CallsListView = Heritage.extend(WidgetMethods, {
|
|||||||
* The line of the respective function.
|
* The line of the respective function.
|
||||||
*/
|
*/
|
||||||
_onStackFileClick: function(e, { file, line }) {
|
_onStackFileClick: function(e, { file, line }) {
|
||||||
viewSourceInDebugger(file, line);
|
this._viewSourceInDebugger(file, line);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -501,7 +501,7 @@ let CallsListView = Heritage.extend(WidgetMethods, {
|
|||||||
}
|
}
|
||||||
let callItem = this.selectedItem;
|
let callItem = this.selectedItem;
|
||||||
let { file, line } = callItem.attachment.actor;
|
let { file, line } = callItem.attachment.actor;
|
||||||
viewSourceInDebugger(file, line);
|
this._viewSourceInDebugger(file, line);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -509,5 +509,18 @@ let CallsListView = Heritage.extend(WidgetMethods, {
|
|||||||
*/
|
*/
|
||||||
_onStepOut: function() {
|
_onStepOut: function() {
|
||||||
this.selectedIndex = this.itemCount - 1;
|
this.selectedIndex = this.itemCount - 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the specified file and line in the debugger. Falls back to Firefox's View Source.
|
||||||
|
*/
|
||||||
|
_viewSourceInDebugger: function (file, line) {
|
||||||
|
gToolbox.viewSourceInDebugger(file, line).then(success => {
|
||||||
|
if (success) {
|
||||||
|
window.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
|
||||||
|
} else {
|
||||||
|
window.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -12,11 +12,13 @@ Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
|||||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||||
|
|
||||||
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
const devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
|
||||||
|
const { require } = devtools;
|
||||||
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||||
const { CallWatcherFront } = require("devtools/server/actors/call-watcher");
|
const { CallWatcherFront } = require("devtools/server/actors/call-watcher");
|
||||||
const { CanvasFront } = require("devtools/server/actors/canvas");
|
const { CanvasFront } = require("devtools/server/actors/canvas");
|
||||||
|
|
||||||
const Telemetry = require("devtools/shared/telemetry");
|
const Telemetry = require("devtools/shared/telemetry");
|
||||||
const telemetry = new Telemetry();
|
const telemetry = new Telemetry();
|
||||||
|
|
||||||
@ -354,32 +356,3 @@ function getThumbnailForCall(thumbnails, index) {
|
|||||||
}
|
}
|
||||||
return CanvasFront.INVALID_SNAPSHOT_IMAGE;
|
return CanvasFront.INVALID_SNAPSHOT_IMAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens/selects the debugger in this toolbox and jumps to the specified
|
|
||||||
* file name and line number.
|
|
||||||
*/
|
|
||||||
function viewSourceInDebugger(url, line) {
|
|
||||||
let showSource = ({ DebuggerView }) => {
|
|
||||||
let item = DebuggerView.Sources.getItemForAttachment(a => a.source.url === url);
|
|
||||||
if (item) {
|
|
||||||
DebuggerView.setEditorLocation(item.attachment.source.actor, line, { noDebug: true }).then(() => {
|
|
||||||
window.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
|
|
||||||
}, () => {
|
|
||||||
window.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the Debugger was already open, switch to it and try to show the
|
|
||||||
// source immediately. Otherwise, initialize it and wait for the sources
|
|
||||||
// to be added first.
|
|
||||||
let debuggerAlreadyOpen = gToolbox.getPanel("jsdebugger");
|
|
||||||
gToolbox.selectTool("jsdebugger").then(({ panelWin: dbg }) => {
|
|
||||||
if (debuggerAlreadyOpen) {
|
|
||||||
showSource(dbg);
|
|
||||||
} else {
|
|
||||||
dbg.once(dbg.EVENTS.SOURCES_ADDED, () => showSource(dbg));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
@ -1222,7 +1222,7 @@ SourceScripts.prototype = {
|
|||||||
* Callback for the debugger's active thread getSources() method.
|
* Callback for the debugger's active thread getSources() method.
|
||||||
*/
|
*/
|
||||||
_onSourcesAdded: function(aResponse) {
|
_onSourcesAdded: function(aResponse) {
|
||||||
if (aResponse.error) {
|
if (aResponse.error || !aResponse.sources) {
|
||||||
let msg = "Error getting sources: " + aResponse.message;
|
let msg = "Error getting sources: " + aResponse.message;
|
||||||
Cu.reportError(msg);
|
Cu.reportError(msg);
|
||||||
dumpn(msg);
|
dumpn(msg);
|
||||||
|
@ -70,13 +70,22 @@ DevTools.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
set testing(state) {
|
set testing(state) {
|
||||||
|
let oldState = this._testing;
|
||||||
this._testing = state;
|
this._testing = state;
|
||||||
|
|
||||||
if (state) {
|
if (state !== oldState) {
|
||||||
// dom.send_after_paint_to_content is set to true (non-default) in
|
if (state) {
|
||||||
// testing/profiles/prefs_general.js so lets set it to the same as it is
|
this._savedSendAfterPaintToContentPref =
|
||||||
// in a default browser profile for the duration of the test.
|
Services.prefs.getBoolPref("dom.send_after_paint_to_content");
|
||||||
Services.prefs.setBoolPref("dom.send_after_paint_to_content", false);
|
|
||||||
|
// dom.send_after_paint_to_content is set to true (non-default) in
|
||||||
|
// testing/profiles/prefs_general.js so lets set it to the same as it is
|
||||||
|
// in a default browser profile for the duration of the test.
|
||||||
|
Services.prefs.setBoolPref("dom.send_after_paint_to_content", false);
|
||||||
|
} else {
|
||||||
|
Services.prefs.setBoolPref("dom.send_after_paint_to_content",
|
||||||
|
this._savedSendAfterPaintToContentPref);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -6,9 +6,11 @@ support-files =
|
|||||||
browser_toolbox_options_disable_js_iframe.html
|
browser_toolbox_options_disable_js_iframe.html
|
||||||
browser_toolbox_options_disable_cache.sjs
|
browser_toolbox_options_disable_cache.sjs
|
||||||
browser_toolbox_sidebar_tool.xul
|
browser_toolbox_sidebar_tool.xul
|
||||||
|
code_math.js
|
||||||
head.js
|
head.js
|
||||||
helper_disable_cache.js
|
helper_disable_cache.js
|
||||||
doc_theme.css
|
doc_theme.css
|
||||||
|
doc_viewsource.html
|
||||||
browser_toolbox_options_enable_serviceworkers_testing.html
|
browser_toolbox_options_enable_serviceworkers_testing.html
|
||||||
serviceworker.js
|
serviceworker.js
|
||||||
|
|
||||||
@ -48,6 +50,10 @@ skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
|
|||||||
[browser_toolbox_tool_ready.js]
|
[browser_toolbox_tool_ready.js]
|
||||||
[browser_toolbox_tool_remote_reopen.js]
|
[browser_toolbox_tool_remote_reopen.js]
|
||||||
[browser_toolbox_transport_events.js]
|
[browser_toolbox_transport_events.js]
|
||||||
|
[browser_toolbox_view_source_01.js]
|
||||||
|
[browser_toolbox_view_source_02.js]
|
||||||
|
[browser_toolbox_view_source_03.js]
|
||||||
|
[browser_toolbox_view_source_04.js]
|
||||||
[browser_toolbox_window_reload_target.js]
|
[browser_toolbox_window_reload_target.js]
|
||||||
[browser_toolbox_window_shortcuts.js]
|
[browser_toolbox_window_shortcuts.js]
|
||||||
skip-if = os == "mac" && os_version == "10.8" || os == "win" && os_version == "5.1" # Bug 851129 - Re-enable browser_toolbox_window_shortcuts.js test after leaks are fixed
|
skip-if = os == "mac" && os_version == "10.8" || os == "win" && os_version == "5.1" # Bug 851129 - Re-enable browser_toolbox_window_shortcuts.js test after leaks are fixed
|
||||||
@ -56,7 +62,7 @@ skip-if = os == "mac" && os_version == "10.8" || os == "win" && os_version == "5
|
|||||||
[browser_toolbox_custom_host.js]
|
[browser_toolbox_custom_host.js]
|
||||||
[browser_toolbox_theme_registration.js]
|
[browser_toolbox_theme_registration.js]
|
||||||
[browser_toolbox_options_enable_serviceworkers_testing.js]
|
[browser_toolbox_options_enable_serviceworkers_testing.js]
|
||||||
skip-if = e10s # Bug 1030318
|
skip-if = true # Bug 1153407 - this test breaks subsequent tests and is not e10s compatible
|
||||||
|
|
||||||
# We want this test to run for mochitest-dt as well, so we include it here:
|
# We want this test to run for mochitest-dt as well, so we include it here:
|
||||||
[../../../base/content/test/general/browser_parsable_css.js]
|
[../../../base/content/test/general/browser_parsable_css.js]
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that Toolbox#viewSourceInDebugger works when debugger is not
|
||||||
|
* yet opened.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let URL = `${URL_ROOT}doc_viewsource.html`;
|
||||||
|
let JS_URL = `${URL_ROOT}code_math.js`;
|
||||||
|
|
||||||
|
function *viewSource() {
|
||||||
|
let toolbox = yield loadToolbox(URL);
|
||||||
|
|
||||||
|
yield toolbox.viewSourceInDebugger(JS_URL, 2);
|
||||||
|
|
||||||
|
let debuggerPanel = toolbox.getPanel("jsdebugger");
|
||||||
|
ok(debuggerPanel, "The debugger panel was opened.");
|
||||||
|
is(toolbox.currentToolId, "jsdebugger", "The debugger panel was selected.");
|
||||||
|
|
||||||
|
let { DebuggerView } = debuggerPanel.panelWin;
|
||||||
|
let Sources = DebuggerView.Sources;
|
||||||
|
|
||||||
|
is(Sources.selectedValue, getSourceActor(Sources, JS_URL),
|
||||||
|
"The correct source is shown in the debugger.");
|
||||||
|
is(DebuggerView.editor.getCursor().line + 1, 2,
|
||||||
|
"The correct line is highlighted in the debugger's source editor.");
|
||||||
|
|
||||||
|
yield unloadToolbox(toolbox);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test () {
|
||||||
|
Task.spawn(viewSource).then(finish, (aError) => {
|
||||||
|
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that Toolbox#viewSourceInDebugger works when debugger is already loaded.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let URL = `${URL_ROOT}doc_viewsource.html`;
|
||||||
|
let JS_URL = `${URL_ROOT}code_math.js`;
|
||||||
|
|
||||||
|
function *viewSource() {
|
||||||
|
let toolbox = yield loadToolbox(URL);
|
||||||
|
let { panelWin: debuggerWin } = yield toolbox.selectTool("jsdebugger");
|
||||||
|
let debuggerEvents = debuggerWin.EVENTS;
|
||||||
|
let { DebuggerView } = debuggerWin;
|
||||||
|
let Sources = DebuggerView.Sources;
|
||||||
|
|
||||||
|
yield debuggerWin.once(debuggerEvents.SOURCE_SHOWN);
|
||||||
|
ok("A source was shown in the debugger.");
|
||||||
|
|
||||||
|
is(Sources.selectedValue, getSourceActor(Sources, JS_URL),
|
||||||
|
"The correct source is initially shown in the debugger.");
|
||||||
|
is(DebuggerView.editor.getCursor().line, 0,
|
||||||
|
"The correct line is initially highlighted in the debugger's source editor.");
|
||||||
|
|
||||||
|
yield toolbox.viewSourceInDebugger(JS_URL, 2);
|
||||||
|
|
||||||
|
let debuggerPanel = toolbox.getPanel("jsdebugger");
|
||||||
|
ok(debuggerPanel, "The debugger panel was opened.");
|
||||||
|
is(toolbox.currentToolId, "jsdebugger", "The debugger panel was selected.");
|
||||||
|
|
||||||
|
is(Sources.selectedValue, getSourceActor(Sources, JS_URL),
|
||||||
|
"The correct source is shown in the debugger.");
|
||||||
|
is(DebuggerView.editor.getCursor().line + 1, 2,
|
||||||
|
"The correct line is highlighted in the debugger's source editor.");
|
||||||
|
|
||||||
|
yield unloadToolbox(toolbox);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test () {
|
||||||
|
Task.spawn(viewSource).then(finish, (aError) => {
|
||||||
|
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that Toolbox#viewSourceInStyleEditor works when style editor is not
|
||||||
|
* yet opened.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let URL = `${URL_ROOT}doc_viewsource.html`;
|
||||||
|
let CSS_URL = `${URL_ROOT}doc_theme.css`;
|
||||||
|
|
||||||
|
function *viewSource() {
|
||||||
|
let toolbox = yield loadToolbox(URL);
|
||||||
|
|
||||||
|
let fileFound = yield toolbox.viewSourceInStyleEditor(CSS_URL, 2);
|
||||||
|
ok(fileFound, "viewSourceInStyleEditor should resolve to true if source found.");
|
||||||
|
|
||||||
|
let stylePanel = toolbox.getPanel("styleeditor");
|
||||||
|
ok(stylePanel, "The style editor panel was opened.");
|
||||||
|
is(toolbox.currentToolId, "styleeditor", "The style editor panel was selected.");
|
||||||
|
|
||||||
|
let { UI } = stylePanel;
|
||||||
|
|
||||||
|
is(UI.selectedEditor.styleSheet.href, CSS_URL,
|
||||||
|
"The correct source is shown in the style editor.");
|
||||||
|
is(UI.selectedEditor.sourceEditor.getCursor().line + 1, 2,
|
||||||
|
"The correct line is highlighted in the style editor's source editor.");
|
||||||
|
|
||||||
|
yield unloadToolbox(toolbox);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test () {
|
||||||
|
Task.spawn(viewSource).then(finish, (aError) => {
|
||||||
|
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that Toolbox#viewSourceInScratchpad works.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let URL = `${URL_ROOT}doc_viewsource.html`;
|
||||||
|
|
||||||
|
function *viewSource() {
|
||||||
|
let toolbox = yield loadToolbox(URL);
|
||||||
|
let win = yield openScratchpadWindow();
|
||||||
|
let { Scratchpad: scratchpad } = win;
|
||||||
|
|
||||||
|
// Brahm's Cello Sonata No.1, Op.38 now in the scratchpad
|
||||||
|
scratchpad.setText("E G B C B\nA B A G A B\nG E");
|
||||||
|
let scratchpadURL = scratchpad.uniqueName;
|
||||||
|
|
||||||
|
// Now select another tool for focus
|
||||||
|
yield toolbox.selectTool("webconsole");
|
||||||
|
|
||||||
|
yield toolbox.viewSourceInScratchpad(scratchpadURL, 2);
|
||||||
|
|
||||||
|
is(scratchpad.editor.getCursor().line, 2,
|
||||||
|
"The correct line is highlighted in scratchpad's editor.");
|
||||||
|
|
||||||
|
win.close();
|
||||||
|
yield unloadToolbox(toolbox);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test () {
|
||||||
|
Task.spawn(viewSource).then(finish, (aError) => {
|
||||||
|
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
}
|
4
browser/devtools/framework/test/code_math.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
function add(a, b, k) {
|
||||||
|
var result = a + b;
|
||||||
|
return k(result);
|
||||||
|
}
|
13
browser/devtools/framework/test/doc_viewsource.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Toolbox test for View Source methods</title>
|
||||||
|
<!-- Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||||
|
<link charset="UTF-8" rel="stylesheet" href="doc_theme.css" />
|
||||||
|
<script src="code_math.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -7,6 +7,7 @@ let TargetFactory = gDevTools.TargetFactory;
|
|||||||
const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||||
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||||
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||||
|
const { ScratchpadManager } = Cu.import("resource:///modules/devtools/scratchpad-manager.jsm", {});
|
||||||
|
|
||||||
const URL_ROOT = "http://example.com/browser/browser/devtools/framework/test/";
|
const URL_ROOT = "http://example.com/browser/browser/devtools/framework/test/";
|
||||||
const CHROME_URL_ROOT = "chrome://mochitests/content/browser/browser/devtools/framework/test/";
|
const CHROME_URL_ROOT = "chrome://mochitests/content/browser/browser/devtools/framework/test/";
|
||||||
@ -175,3 +176,49 @@ function getChromeActors(callback)
|
|||||||
DebuggerServer.destroy();
|
DebuggerServer.destroy();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadToolbox (url) {
|
||||||
|
let { promise: p, resolve } = promise.defer();
|
||||||
|
gBrowser.selectedTab = gBrowser.addTab();
|
||||||
|
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||||
|
|
||||||
|
gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
|
||||||
|
gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
|
||||||
|
gDevTools.showToolbox(target).then(resolve);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
content.location = url;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unloadToolbox (toolbox) {
|
||||||
|
return toolbox.destroy().then(function() {
|
||||||
|
gBrowser.removeCurrentTab();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceActor(aSources, aURL) {
|
||||||
|
let item = aSources.getItemForAttachment(a => a.source.url === aURL);
|
||||||
|
return item && item.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a Scratchpad window.
|
||||||
|
*
|
||||||
|
* @return nsIDOMWindow
|
||||||
|
* The new window object that holds Scratchpad.
|
||||||
|
*/
|
||||||
|
function *openScratchpadWindow () {
|
||||||
|
let { promise: p, resolve } = promise.defer();
|
||||||
|
let win = ScratchpadManager.openScratchpad();
|
||||||
|
|
||||||
|
yield once(win, "load");
|
||||||
|
|
||||||
|
win.Scratchpad.addObserver({
|
||||||
|
onReady: function () {
|
||||||
|
win.Scratchpad.removeObserver(this);
|
||||||
|
resolve(win);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ let Telemetry = require("devtools/shared/telemetry");
|
|||||||
let {getHighlighterUtils} = require("devtools/framework/toolbox-highlighter-utils");
|
let {getHighlighterUtils} = require("devtools/framework/toolbox-highlighter-utils");
|
||||||
let HUDService = require("devtools/webconsole/hudservice");
|
let HUDService = require("devtools/webconsole/hudservice");
|
||||||
let {showDoorhanger} = require("devtools/shared/doorhanger");
|
let {showDoorhanger} = require("devtools/shared/doorhanger");
|
||||||
|
let sourceUtils = require("devtools/shared/source-utils");
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
@ -380,11 +381,14 @@ Toolbox.prototype = {
|
|||||||
framesPromise
|
framesPromise
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Lazily connect to the profiler here and don't wait for it to complete,
|
||||||
|
// used to intercept console.profile calls before the performance tools are open.
|
||||||
let profilerReady = this._connectProfiler();
|
let profilerReady = this._connectProfiler();
|
||||||
|
|
||||||
// Only wait for the profiler initialization during tests. Otherwise,
|
// However, while testing, we must wait for the performance connection to finish,
|
||||||
// lazily load this. This is to intercept console.profile calls; the performance
|
// as most tests shut down without waiting for a toolbox destruction event,
|
||||||
// tools will explicitly wait for the connection opening when opened.
|
// resulting in the shared profiler connection being opened and closed
|
||||||
|
// outside of the test that originally opened the toolbox.
|
||||||
if (gDevTools.testing) {
|
if (gDevTools.testing) {
|
||||||
yield profilerReady;
|
yield profilerReady;
|
||||||
}
|
}
|
||||||
@ -1708,6 +1712,7 @@ Toolbox.prototype = {
|
|||||||
gDevTools.off("pref-changed", this._prefChanged);
|
gDevTools.off("pref-changed", this._prefChanged);
|
||||||
|
|
||||||
this._lastFocusedElement = null;
|
this._lastFocusedElement = null;
|
||||||
|
|
||||||
if (this.webconsolePanel) {
|
if (this.webconsolePanel) {
|
||||||
this._saveSplitConsoleHeight();
|
this._saveSplitConsoleHeight();
|
||||||
this.webconsolePanel.removeEventListener("resize",
|
this.webconsolePanel.removeEventListener("resize",
|
||||||
@ -1866,7 +1871,9 @@ Toolbox.prototype = {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnects the underlying Performance Actor Connection.
|
* Disconnects the underlying Performance Actor Connection. If the connection
|
||||||
|
* has not finished initializing, as opening a toolbox does not wait,
|
||||||
|
* the performance connection destroy method will wait for it on its own.
|
||||||
*/
|
*/
|
||||||
_disconnectProfiler: Task.async(function*() {
|
_disconnectProfiler: Task.async(function*() {
|
||||||
if (!this._performanceConnection) {
|
if (!this._performanceConnection) {
|
||||||
@ -1875,4 +1882,48 @@ Toolbox.prototype = {
|
|||||||
yield this._performanceConnection.destroy();
|
yield this._performanceConnection.destroy();
|
||||||
this._performanceConnection = null;
|
this._performanceConnection = null;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns gViewSourceUtils for viewing source.
|
||||||
|
*/
|
||||||
|
get gViewSourceUtils() {
|
||||||
|
return this.frame.contentWindow.gViewSourceUtils;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens source in style editor. Falls back to plain "view-source:".
|
||||||
|
* @see browser/devtools/shared/source-utils.js
|
||||||
|
*/
|
||||||
|
viewSourceInStyleEditor: function (sourceURL, sourceLine) {
|
||||||
|
return sourceUtils.viewSourceInStyleEditor(this, sourceURL, sourceLine);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens source in debugger. Falls back to plain "view-source:".
|
||||||
|
* @see browser/devtools/shared/source-utils.js
|
||||||
|
*/
|
||||||
|
viewSourceInDebugger: function (sourceURL, sourceLine) {
|
||||||
|
return sourceUtils.viewSourceInDebugger(this, sourceURL, sourceLine);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens source in scratchpad. Falls back to plain "view-source:".
|
||||||
|
* TODO The `sourceURL` for scratchpad instances are like `Scratchpad/1`.
|
||||||
|
* If instances are scoped one-per-browser-window, then we should be able
|
||||||
|
* to infer the URL from this toolbox, or use the built in scratchpad IN
|
||||||
|
* the toolbox.
|
||||||
|
*
|
||||||
|
* @see browser/devtools/shared/source-utils.js
|
||||||
|
*/
|
||||||
|
viewSourceInScratchpad: function (sourceURL, sourceLine) {
|
||||||
|
return sourceUtils.viewSourceInScratchpad(sourceURL, sourceLine);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens source in plain "view-source:".
|
||||||
|
* @see browser/devtools/shared/source-utils.js
|
||||||
|
*/
|
||||||
|
viewSource: function (sourceURL, sourceLine) {
|
||||||
|
return sourceUtils.viewSource(this, sourceURL, sourceLine);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
|
|
||||||
<script type="application/javascript;version=1.8"
|
<script type="application/javascript;version=1.8"
|
||||||
src="chrome://browser/content/devtools/theme-switching.js"/>
|
src="chrome://browser/content/devtools/theme-switching.js"/>
|
||||||
|
<script type="application/javascript"
|
||||||
|
src="chrome://global/content/viewSourceUtils.js"/>
|
||||||
|
|
||||||
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
|
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
|
||||||
|
|
||||||
<commandset id="editMenuCommands"/>
|
<commandset id="editMenuCommands"/>
|
||||||
|
@ -283,13 +283,6 @@ InspectorPanel.prototype = {
|
|||||||
this._target = value;
|
this._target = value;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose gViewSourceUtils so that other tools can make use of them.
|
|
||||||
*/
|
|
||||||
get viewSourceUtils() {
|
|
||||||
return this.panelWin.gViewSourceUtils;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicate that a tool has modified the state of the page. Used to
|
* Indicate that a tool has modified the state of the page. Used to
|
||||||
* decide whether to show the "are you sure you want to navigate"
|
* decide whether to show the "are you sure you want to navigate"
|
||||||
|
@ -18,9 +18,6 @@
|
|||||||
<script type="application/javascript;version=1.8"
|
<script type="application/javascript;version=1.8"
|
||||||
src="chrome://browser/content/devtools/theme-switching.js"/>
|
src="chrome://browser/content/devtools/theme-switching.js"/>
|
||||||
|
|
||||||
<script type="application/javascript"
|
|
||||||
src="chrome://global/content/viewSourceUtils.js"/>
|
|
||||||
|
|
||||||
<commandset>
|
<commandset>
|
||||||
<command id="nodeSearchCommand"
|
<command id="nodeSearchCommand"
|
||||||
oncommand="inspector.searchBox.focus()"/>
|
oncommand="inspector.searchBox.focus()"/>
|
||||||
|
@ -141,6 +141,8 @@ browser.jar:
|
|||||||
content/browser/devtools/spectrum.css (shared/widgets/spectrum.css)
|
content/browser/devtools/spectrum.css (shared/widgets/spectrum.css)
|
||||||
content/browser/devtools/cubic-bezier-frame.xhtml (shared/widgets/cubic-bezier-frame.xhtml)
|
content/browser/devtools/cubic-bezier-frame.xhtml (shared/widgets/cubic-bezier-frame.xhtml)
|
||||||
content/browser/devtools/cubic-bezier.css (shared/widgets/cubic-bezier.css)
|
content/browser/devtools/cubic-bezier.css (shared/widgets/cubic-bezier.css)
|
||||||
|
content/browser/devtools/mdn-docs-frame.xhtml (shared/widgets/mdn-docs-frame.xhtml)
|
||||||
|
content/browser/devtools/mdn-docs.css (shared/widgets/mdn-docs.css)
|
||||||
content/browser/devtools/filter-frame.xhtml (shared/widgets/filter-frame.xhtml)
|
content/browser/devtools/filter-frame.xhtml (shared/widgets/filter-frame.xhtml)
|
||||||
content/browser/devtools/filter-widget.css (shared/widgets/filter-widget.css)
|
content/browser/devtools/filter-widget.css (shared/widgets/filter-widget.css)
|
||||||
content/browser/devtools/eyedropper.xul (eyedropper/eyedropper.xul)
|
content/browser/devtools/eyedropper.xul (eyedropper/eyedropper.xul)
|
||||||
|
@ -4,13 +4,73 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||||
|
loader.lazyRequireGetter(this, "promise");
|
||||||
loader.lazyRequireGetter(this, "EventEmitter",
|
loader.lazyRequireGetter(this, "EventEmitter",
|
||||||
"devtools/toolkit/event-emitter");
|
"devtools/toolkit/event-emitter");
|
||||||
|
loader.lazyRequireGetter(this, "RecordingUtils",
|
||||||
|
"devtools/performance/recording-utils", true);
|
||||||
|
|
||||||
const REQUIRED_MEMORY_ACTOR_METHODS = [
|
const REQUIRED_MEMORY_ACTOR_METHODS = [
|
||||||
"attach", "detach", "startRecordingAllocations", "stopRecordingAllocations", "getAllocations"
|
"attach", "detach", "startRecordingAllocations", "stopRecordingAllocations", "getAllocations"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for a facade around an underlying ProfilerFront.
|
||||||
|
*/
|
||||||
|
function ProfilerFront (target) {
|
||||||
|
this._target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerFront.prototype = {
|
||||||
|
// Connects to the targets underlying real ProfilerFront.
|
||||||
|
connect: Task.async(function*() {
|
||||||
|
let target = this._target;
|
||||||
|
// Chrome and content process targets already have obtained a reference
|
||||||
|
// to the profiler tab actor. Use it immediately.
|
||||||
|
if (target.form && target.form.profilerActor) {
|
||||||
|
this._profiler = target.form.profilerActor;
|
||||||
|
}
|
||||||
|
// Check if we already have a grip to the `listTabs` response object
|
||||||
|
// and, if we do, use it to get to the profiler actor.
|
||||||
|
else if (target.root && target.root.profilerActor) {
|
||||||
|
this._profiler = target.root.profilerActor;
|
||||||
|
}
|
||||||
|
// Otherwise, call `listTabs`.
|
||||||
|
else {
|
||||||
|
this._profiler = (yield listTabs(target.client)).profilerActor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch and store information about the SPS profiler and
|
||||||
|
// server profiler.
|
||||||
|
this.traits = {};
|
||||||
|
this.traits.filterable = target.getTrait("profilerDataFilterable");
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a request to the underlying real profiler actor. Handles
|
||||||
|
* backwards compatibility differences based off of the features
|
||||||
|
* and traits of the actor.
|
||||||
|
*/
|
||||||
|
_request: function (method, ...args) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
let data = args[0] || {};
|
||||||
|
data.to = this._profiler;
|
||||||
|
data.type = method;
|
||||||
|
this._target.client.request(data, res => {
|
||||||
|
// If the backend does not support filtering by start and endtime on platform (< Fx40),
|
||||||
|
// do it on the client (much slower).
|
||||||
|
if (method === "getProfile" && !this.traits.filterable) {
|
||||||
|
RecordingUtils.filterSamples(res.profile, data.startTime || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
deferred.resolve(res);
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.ProfilerFront = ProfilerFront;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A dummy front decorated with the provided methods.
|
* A dummy front decorated with the provided methods.
|
||||||
*
|
*
|
||||||
@ -107,3 +167,14 @@ function timelineActorSupported(target) {
|
|||||||
return target.hasActor("timeline");
|
return target.hasActor("timeline");
|
||||||
}
|
}
|
||||||
exports.timelineActorSupported = Task.async(timelineActorSupported);
|
exports.timelineActorSupported = Task.async(timelineActorSupported);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a promise resolved with a listing of all the tabs in the
|
||||||
|
* provided thread client.
|
||||||
|
*/
|
||||||
|
function listTabs(client) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
client.listTabs(deferred.resolve);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -144,7 +144,9 @@ PerformanceActorsConnection.prototype = {
|
|||||||
*/
|
*/
|
||||||
destroy: Task.async(function*() {
|
destroy: Task.async(function*() {
|
||||||
if (this._connecting && !this._connected) {
|
if (this._connecting && !this._connected) {
|
||||||
console.warn("Attempting to destroy SharedPerformanceActorsConnection before initialization completion. If testing, ensure `gDevTools.testing` is set.");
|
yield this._connecting.promise;
|
||||||
|
} else if (!this._connected) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield this._unregisterListeners();
|
yield this._unregisterListeners();
|
||||||
@ -152,26 +154,16 @@ PerformanceActorsConnection.prototype = {
|
|||||||
|
|
||||||
this._memory = this._timeline = this._profiler = this._target = this._client = null;
|
this._memory = this._timeline = this._profiler = this._target = this._client = null;
|
||||||
this._connected = false;
|
this._connected = false;
|
||||||
|
this._connecting = null;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a connection to the profiler actor.
|
* Initializes a connection to the profiler actor. Uses a facade around the ProfilerFront
|
||||||
|
* for similarity to the other actors in the shared connection.
|
||||||
*/
|
*/
|
||||||
_connectProfilerActor: Task.async(function*() {
|
_connectProfilerActor: Task.async(function*() {
|
||||||
// Chrome and content process targets already have obtained a reference
|
this._profiler = new compatibility.ProfilerFront(this._target);
|
||||||
// to the profiler tab actor. Use it immediately.
|
yield this._profiler.connect();
|
||||||
if (this._target.form && this._target.form.profilerActor) {
|
|
||||||
this._profiler = this._target.form.profilerActor;
|
|
||||||
}
|
|
||||||
// Check if we already have a grip to the `listTabs` response object
|
|
||||||
// and, if we do, use it to get to the profiler actor.
|
|
||||||
else if (this._target.root && this._target.root.profilerActor) {
|
|
||||||
this._profiler = this._target.root.profilerActor;
|
|
||||||
}
|
|
||||||
// Otherwise, call `listTabs`.
|
|
||||||
else {
|
|
||||||
this._profiler = (yield listTabs(this._client)).profilerActor;
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -253,12 +245,7 @@ PerformanceActorsConnection.prototype = {
|
|||||||
_request: function(actor, method, ...args) {
|
_request: function(actor, method, ...args) {
|
||||||
// Handle requests to the profiler actor.
|
// Handle requests to the profiler actor.
|
||||||
if (actor == "profiler") {
|
if (actor == "profiler") {
|
||||||
let deferred = promise.defer();
|
return this._profiler._request(method, ...args);
|
||||||
let data = args[0] || {};
|
|
||||||
data.to = this._profiler;
|
|
||||||
data.type = method;
|
|
||||||
this._client.request(data, deferred.resolve);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle requests to the timeline actor.
|
// Handle requests to the timeline actor.
|
||||||
@ -331,10 +318,20 @@ PerformanceActorsConnection.prototype = {
|
|||||||
/**
|
/**
|
||||||
* Invoked whenever `console.profileEnd` is called.
|
* Invoked whenever `console.profileEnd` is called.
|
||||||
*
|
*
|
||||||
* @param object profilerData
|
* @param string profileLabel
|
||||||
* The dump of data from the profiler triggered by this console.profileEnd call.
|
* The provided string argument if available; undefined otherwise.
|
||||||
|
* @param number currentTime
|
||||||
|
* The time (in milliseconds) when the call was made, relative to when
|
||||||
|
* the nsIProfiler module was started.
|
||||||
*/
|
*/
|
||||||
_onConsoleProfileEnd: Task.async(function *(profilerData) {
|
_onConsoleProfileEnd: Task.async(function *(data) {
|
||||||
|
// If no data, abort; can occur if profiler isn't running and we get a surprise
|
||||||
|
// call to console.profileEnd()
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let { profileLabel, currentTime: endTime } = data;
|
||||||
|
|
||||||
let pending = this._recordings.filter(r => r.isConsole() && r.isRecording());
|
let pending = this._recordings.filter(r => r.isConsole() && r.isRecording());
|
||||||
if (pending.length === 0) {
|
if (pending.length === 0) {
|
||||||
return;
|
return;
|
||||||
@ -343,8 +340,8 @@ PerformanceActorsConnection.prototype = {
|
|||||||
let model;
|
let model;
|
||||||
// Try to find the corresponding `console.profile` call if
|
// Try to find the corresponding `console.profile` call if
|
||||||
// a label was used in profileEnd(). If no matches, abort.
|
// a label was used in profileEnd(). If no matches, abort.
|
||||||
if (profilerData.profileLabel) {
|
if (profileLabel) {
|
||||||
model = pending.find(e => e.getLabel() === profilerData.profileLabel);
|
model = pending.find(e => e.getLabel() === profileLabel);
|
||||||
}
|
}
|
||||||
// If no label supplied, pop off the most recent pending console recording
|
// If no label supplied, pop off the most recent pending console recording
|
||||||
else {
|
else {
|
||||||
@ -401,7 +398,7 @@ PerformanceActorsConnection.prototype = {
|
|||||||
let model = new RecordingModel(options);
|
let model = new RecordingModel(options);
|
||||||
// All actors are started asynchronously over the remote debugging protocol.
|
// All actors are started asynchronously over the remote debugging protocol.
|
||||||
// Get the corresponding start times from each one of them.
|
// Get the corresponding start times from each one of them.
|
||||||
let profilerStartTime = yield this._startProfiler();
|
let profilerStartTime = yield this._startProfiler(options);
|
||||||
let timelineStartTime = yield this._startTimeline(options);
|
let timelineStartTime = yield this._startTimeline(options);
|
||||||
let memoryStartTime = yield this._startMemory(options);
|
let memoryStartTime = yield this._startMemory(options);
|
||||||
|
|
||||||
@ -445,7 +442,8 @@ PerformanceActorsConnection.prototype = {
|
|||||||
this._recordings.splice(this._recordings.indexOf(model), 1);
|
this._recordings.splice(this._recordings.indexOf(model), 1);
|
||||||
|
|
||||||
let config = model.getConfiguration();
|
let config = model.getConfiguration();
|
||||||
let profilerData = yield this._request("profiler", "getProfile");
|
let startTime = model.getProfilerStartTime();
|
||||||
|
let profilerData = yield this._request("profiler", "getProfile", { startTime });
|
||||||
let memoryEndTime = Date.now();
|
let memoryEndTime = Date.now();
|
||||||
let timelineEndTime = Date.now();
|
let timelineEndTime = Date.now();
|
||||||
|
|
||||||
@ -485,7 +483,7 @@ PerformanceActorsConnection.prototype = {
|
|||||||
/**
|
/**
|
||||||
* Starts the profiler actor, if necessary.
|
* Starts the profiler actor, if necessary.
|
||||||
*/
|
*/
|
||||||
_startProfiler: Task.async(function *() {
|
_startProfiler: Task.async(function *(options={}) {
|
||||||
// Start the profiler only if it wasn't already active. The built-in
|
// Start the profiler only if it wasn't already active. The built-in
|
||||||
// nsIPerformance module will be kept recording, because it's the same instance
|
// nsIPerformance module will be kept recording, because it's the same instance
|
||||||
// for all targets and interacts with the whole platform, so we don't want
|
// for all targets and interacts with the whole platform, so we don't want
|
||||||
@ -496,10 +494,13 @@ PerformanceActorsConnection.prototype = {
|
|||||||
return profilerStatus.currentTime;
|
return profilerStatus.currentTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this._customProfilerOptions is defined, use those to pass in
|
// Translate options from the recording model into profiler-specific
|
||||||
// to the profiler actor. The profiler actor handles all the defaults
|
// options for the nsIProfiler
|
||||||
// now, so this should only be used for tests.
|
let profilerOptions = {
|
||||||
let profilerOptions = this._customProfilerOptions || {};
|
entries: options.bufferSize,
|
||||||
|
interval: options.sampleFrequency ? (1000 / (options.sampleFrequency * 1000)) : void 0
|
||||||
|
};
|
||||||
|
|
||||||
yield this._request("profiler", "startProfiler", profilerOptions);
|
yield this._request("profiler", "startProfiler", profilerOptions);
|
||||||
|
|
||||||
this.emit("profiler-activated");
|
this.emit("profiler-activated");
|
||||||
@ -667,16 +668,6 @@ PerformanceFront.prototype = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a promise resolved with a listing of all the tabs in the
|
|
||||||
* provided thread client.
|
|
||||||
*/
|
|
||||||
function listTabs(client) {
|
|
||||||
let deferred = promise.defer();
|
|
||||||
client.listTabs(deferred.resolve);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an object of configurations based off of preferences for a RecordingModel.
|
* Creates an object of configurations based off of preferences for a RecordingModel.
|
||||||
*/
|
*/
|
||||||
|
@ -26,7 +26,9 @@ const RecordingModel = function (options={}) {
|
|||||||
withMemory: options.withMemory || false,
|
withMemory: options.withMemory || false,
|
||||||
withAllocations: options.withAllocations || false,
|
withAllocations: options.withAllocations || false,
|
||||||
allocationsSampleProbability: options.allocationsSampleProbability || 0,
|
allocationsSampleProbability: options.allocationsSampleProbability || 0,
|
||||||
allocationsMaxLogLength: options.allocationsMaxLogLength || 0
|
allocationsMaxLogLength: options.allocationsMaxLogLength || 0,
|
||||||
|
bufferSize: options.bufferSize || 0,
|
||||||
|
sampleFrequency: options.sampleFrequency || 1
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -113,10 +115,10 @@ RecordingModel.prototype = {
|
|||||||
this._duration = info.profilerEndTime - this._profilerStartTime;
|
this._duration = info.profilerEndTime - this._profilerStartTime;
|
||||||
this._recording = false;
|
this._recording = false;
|
||||||
|
|
||||||
// We'll need to filter out all samples that fall out of current profile's
|
// We filter out all samples that fall out of current profile's range
|
||||||
// range since the profiler is continuously running. Because of this, sample
|
// since the profiler is continuously running. Because of this, sample
|
||||||
// times are not guaranteed to have a zero epoch, so offset the timestamps.
|
// times are not guaranteed to have a zero epoch, so offset the
|
||||||
RecordingUtils.filterSamples(this._profile, this._profilerStartTime);
|
// timestamps.
|
||||||
RecordingUtils.offsetSampleTimes(this._profile, this._profilerStartTime);
|
RecordingUtils.offsetSampleTimes(this._profile, this._profilerStartTime);
|
||||||
|
|
||||||
// Markers need to be sorted ascending by time, to be properly displayed
|
// Markers need to be sorted ascending by time, to be properly displayed
|
||||||
@ -124,6 +126,14 @@ RecordingModel.prototype = {
|
|||||||
this._markers = this._markers.sort((a, b) => (a.start > b.start));
|
this._markers = this._markers.sort((a, b) => (a.start > b.start));
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the profile's start time.
|
||||||
|
* @return number
|
||||||
|
*/
|
||||||
|
getProfilerStartTime: function () {
|
||||||
|
return this._profilerStartTime;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the profile's label, from `console.profile(LABEL)`.
|
* Gets the profile's label, from `console.profile(LABEL)`.
|
||||||
* @return string
|
* @return string
|
||||||
|
@ -206,7 +206,9 @@ let PerformanceController = {
|
|||||||
this._nonBooleanPrefs = new ViewHelpers.Prefs("devtools.performance", {
|
this._nonBooleanPrefs = new ViewHelpers.Prefs("devtools.performance", {
|
||||||
"hidden-markers": ["Json", "timeline.hidden-markers"],
|
"hidden-markers": ["Json", "timeline.hidden-markers"],
|
||||||
"memory-sample-probability": ["Float", "memory.sample-probability"],
|
"memory-sample-probability": ["Float", "memory.sample-probability"],
|
||||||
"memory-max-log-length": ["Int", "memory.max-log-length"]
|
"memory-max-log-length": ["Int", "memory.max-log-length"],
|
||||||
|
"profiler-buffer-size": ["Int", "profiler.buffer-size"],
|
||||||
|
"profiler-sample-frequency": ["Int", "profiler.sample-frequency-khz"],
|
||||||
});
|
});
|
||||||
|
|
||||||
this._nonBooleanPrefs.registerObserver();
|
this._nonBooleanPrefs.registerObserver();
|
||||||
@ -293,7 +295,9 @@ let PerformanceController = {
|
|||||||
withTicks: this.getOption("enable-framerate"),
|
withTicks: this.getOption("enable-framerate"),
|
||||||
withAllocations: this.getOption("enable-memory"),
|
withAllocations: this.getOption("enable-memory"),
|
||||||
allocationsSampleProbability: this.getPref("memory-sample-probability"),
|
allocationsSampleProbability: this.getPref("memory-sample-probability"),
|
||||||
allocationsMaxLogLength: this.getPref("memory-max-log-length")
|
allocationsMaxLogLength: this.getPref("memory-max-log-length"),
|
||||||
|
bufferSize: this.getPref("profiler-buffer-size"),
|
||||||
|
sampleFrequency: this.getPref("profiler-sample-frequency")
|
||||||
};
|
};
|
||||||
|
|
||||||
this.emit(EVENTS.RECORDING_WILL_START);
|
this.emit(EVENTS.RECORDING_WILL_START);
|
||||||
|
@ -19,6 +19,7 @@ support-files =
|
|||||||
[browser_perf-compatibility-02.js]
|
[browser_perf-compatibility-02.js]
|
||||||
[browser_perf-compatibility-03.js]
|
[browser_perf-compatibility-03.js]
|
||||||
[browser_perf-compatibility-04.js]
|
[browser_perf-compatibility-04.js]
|
||||||
|
[browser_perf-compatibility-05.js]
|
||||||
[browser_perf-clear-01.js]
|
[browser_perf-clear-01.js]
|
||||||
[browser_perf-clear-02.js]
|
[browser_perf-clear-02.js]
|
||||||
[browser_perf-columns-js-calltree.js]
|
[browser_perf-columns-js-calltree.js]
|
||||||
@ -57,8 +58,6 @@ support-files =
|
|||||||
[browser_perf-jit-view-02.js]
|
[browser_perf-jit-view-02.js]
|
||||||
[browser_perf-jit-model-01.js]
|
[browser_perf-jit-model-01.js]
|
||||||
[browser_perf-jit-model-02.js]
|
[browser_perf-jit-model-02.js]
|
||||||
[browser_perf-jump-to-debugger-01.js]
|
|
||||||
[browser_perf-jump-to-debugger-02.js]
|
|
||||||
[browser_perf-options-01.js]
|
[browser_perf-options-01.js]
|
||||||
[browser_perf-options-02.js]
|
[browser_perf-options-02.js]
|
||||||
[browser_perf-options-invert-call-tree-01.js]
|
[browser_perf-options-invert-call-tree-01.js]
|
||||||
@ -75,6 +74,7 @@ support-files =
|
|||||||
[browser_perf-options-enable-memory-02.js]
|
[browser_perf-options-enable-memory-02.js]
|
||||||
[browser_perf-options-enable-framerate.js]
|
[browser_perf-options-enable-framerate.js]
|
||||||
[browser_perf-options-allocations.js]
|
[browser_perf-options-allocations.js]
|
||||||
|
[browser_perf-options-profiler.js]
|
||||||
[browser_perf-overview-render-01.js]
|
[browser_perf-overview-render-01.js]
|
||||||
[browser_perf-overview-render-02.js]
|
[browser_perf-overview-render-02.js]
|
||||||
[browser_perf-overview-render-03.js]
|
[browser_perf-overview-render-03.js]
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that when using an older server (< Fx40) where the profiler actor does not
|
||||||
|
* have the `filterable` trait, the samples are filtered by time on the client, rather
|
||||||
|
* than the more performant platform code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const WAIT_TIME = 1000; // ms
|
||||||
|
|
||||||
|
function spawnTest () {
|
||||||
|
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||||
|
let { gFront: front, gTarget: target } = panel.panelWin;
|
||||||
|
let connection = getPerformanceActorsConnection(target);
|
||||||
|
|
||||||
|
// Explicitly override the profiler's trait `filterable`
|
||||||
|
connection._profiler.traits.filterable = false;
|
||||||
|
|
||||||
|
// Ugly, but we need to also not send the startTime to the server
|
||||||
|
// so we don't filter it both on the server and client
|
||||||
|
let request = target.client.request;
|
||||||
|
target.client.request = (data, res) => {
|
||||||
|
// Copy so we don't destructively change the request object, as we use
|
||||||
|
// the startTime on this object for filtering in the callback
|
||||||
|
let newData = merge({}, data, { startTime: void 0 });
|
||||||
|
request.call(target.client, newData, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Perform the first recording...
|
||||||
|
|
||||||
|
let firstRecording = yield front.startRecording();
|
||||||
|
let firstRecordingStartTime = firstRecording._profilerStartTime;
|
||||||
|
info("Started profiling at: " + firstRecordingStartTime);
|
||||||
|
|
||||||
|
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
|
||||||
|
|
||||||
|
yield front.stopRecording(firstRecording);
|
||||||
|
|
||||||
|
is(firstRecordingStartTime, 0,
|
||||||
|
"The profiling start time should be 0 for the first recording.");
|
||||||
|
ok(firstRecording.getDuration() >= WAIT_TIME,
|
||||||
|
"The first recording duration is correct.");
|
||||||
|
|
||||||
|
// Perform the second recording...
|
||||||
|
|
||||||
|
let secondRecording = yield front.startRecording();
|
||||||
|
let secondRecordingStartTime = secondRecording._profilerStartTime;
|
||||||
|
info("Started profiling at: " + secondRecordingStartTime);
|
||||||
|
|
||||||
|
busyWait(WAIT_TIME); // allow the profiler module to sample more cpu activity
|
||||||
|
|
||||||
|
yield front.stopRecording(secondRecording);
|
||||||
|
let secondRecordingProfile = secondRecording.getProfile();
|
||||||
|
let secondRecordingSamples = secondRecordingProfile.threads[0].samples;
|
||||||
|
|
||||||
|
isnot(secondRecording._profilerStartTime, 0,
|
||||||
|
"The profiling start time should not be 0 on the second recording.");
|
||||||
|
ok(secondRecording.getDuration() >= WAIT_TIME,
|
||||||
|
"The second recording duration is correct.");
|
||||||
|
|
||||||
|
info("Second profile's first sample time: " + secondRecordingSamples[0].time);
|
||||||
|
ok(secondRecordingSamples[0].time < secondRecordingStartTime,
|
||||||
|
"The second recorded sample times were normalized.");
|
||||||
|
ok(secondRecordingSamples[0].time > 0,
|
||||||
|
"The second recorded sample times were normalized correctly.");
|
||||||
|
ok(!secondRecordingSamples.find(e => e.time + secondRecordingStartTime <= firstRecording.getDuration()),
|
||||||
|
"There should be no samples from the first recording in the second one, " +
|
||||||
|
"even though the total number of frames did not overflow.");
|
||||||
|
|
||||||
|
target.client.request = request;
|
||||||
|
|
||||||
|
yield teardown(panel);
|
||||||
|
finish();
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
|
||||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests if the performance tool can jump to the debugger.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function spawnTest () {
|
|
||||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
|
||||||
let { viewSourceInDebugger } = panel.panelWin;
|
|
||||||
|
|
||||||
yield viewSourceInDebugger(SIMPLE_URL, 14);
|
|
||||||
|
|
||||||
let debuggerPanel = toolbox.getPanel("jsdebugger");
|
|
||||||
ok(debuggerPanel, "The debugger panel was opened.");
|
|
||||||
|
|
||||||
let { DebuggerView } = debuggerPanel.panelWin;
|
|
||||||
let Sources = DebuggerView.Sources;
|
|
||||||
|
|
||||||
is(Sources.selectedValue, getSourceActor(Sources, SIMPLE_URL),
|
|
||||||
"The correct source is shown in the debugger.");
|
|
||||||
is(DebuggerView.editor.getCursor().line + 1, 14,
|
|
||||||
"The correct line is highlighted in the debugger's source editor.");
|
|
||||||
|
|
||||||
yield teardown(panel);
|
|
||||||
finish();
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
|
||||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests if the performance tool can jump to the debugger, when the source was
|
|
||||||
* already loaded in that tool.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function spawnTest() {
|
|
||||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "jsdebugger");
|
|
||||||
let debuggerWin = panel.panelWin;
|
|
||||||
let debuggerEvents = debuggerWin.EVENTS;
|
|
||||||
let { DebuggerView } = debuggerWin;
|
|
||||||
let Sources = DebuggerView.Sources;
|
|
||||||
|
|
||||||
yield debuggerWin.once(debuggerEvents.SOURCE_SHOWN);
|
|
||||||
ok("A source was shown in the debugger.");
|
|
||||||
|
|
||||||
is(Sources.selectedValue, getSourceActor(Sources, SIMPLE_URL),
|
|
||||||
"The correct source is initially shown in the debugger.");
|
|
||||||
is(DebuggerView.editor.getCursor().line, 0,
|
|
||||||
"The correct line is initially highlighted in the debugger's source editor.");
|
|
||||||
|
|
||||||
yield toolbox.selectTool("performance");
|
|
||||||
let perfPanel = toolbox.getCurrentPanel();
|
|
||||||
let perfWin = perfPanel.panelWin;
|
|
||||||
let { viewSourceInDebugger } = perfWin;
|
|
||||||
|
|
||||||
yield viewSourceInDebugger(SIMPLE_URL, 14);
|
|
||||||
|
|
||||||
panel = toolbox.getPanel("jsdebugger");
|
|
||||||
ok(panel, "The debugger panel was reselected.");
|
|
||||||
|
|
||||||
is(DebuggerView.Sources.selectedValue, getSourceActor(Sources, SIMPLE_URL),
|
|
||||||
"The correct source is still shown in the debugger.");
|
|
||||||
is(DebuggerView.editor.getCursor().line + 1, 14,
|
|
||||||
"The correct line is now highlighted in the debugger's source editor.");
|
|
||||||
|
|
||||||
yield teardown(perfPanel);
|
|
||||||
finish();
|
|
||||||
}
|
|
@ -0,0 +1,26 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that setting the `devtools.performance.profiler.` prefs propagate to the profiler actor.
|
||||||
|
*/
|
||||||
|
function spawnTest () {
|
||||||
|
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||||
|
let { gFront } = panel.panelWin;
|
||||||
|
|
||||||
|
Services.prefs.setIntPref(PROFILER_BUFFER_SIZE_PREF, 1000);
|
||||||
|
Services.prefs.setIntPref(PROFILER_SAMPLE_RATE_PREF, 2);
|
||||||
|
|
||||||
|
yield startRecording(panel);
|
||||||
|
|
||||||
|
let { entries, interval } = yield gFront._request("profiler", "getStartOptions");
|
||||||
|
|
||||||
|
yield stopRecording(panel);
|
||||||
|
|
||||||
|
is(entries, 1000, "profiler entries option is set on profiler");
|
||||||
|
is(interval, 0.5, "profiler interval option is set on profiler");
|
||||||
|
|
||||||
|
yield teardown(panel);
|
||||||
|
finish();
|
||||||
|
}
|
@ -5,7 +5,7 @@
|
|||||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||||
|
|
||||||
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||||
|
let { Preferences } = Cu.import("resource://gre/modules/Preferences.jsm", {});
|
||||||
let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||||
let { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
let { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||||
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||||
@ -25,6 +25,8 @@ const SIMPLE_URL = EXAMPLE_URL + "doc_simple-test.html";
|
|||||||
|
|
||||||
const MEMORY_SAMPLE_PROB_PREF = "devtools.performance.memory.sample-probability";
|
const MEMORY_SAMPLE_PROB_PREF = "devtools.performance.memory.sample-probability";
|
||||||
const MEMORY_MAX_LOG_LEN_PREF = "devtools.performance.memory.max-log-length";
|
const MEMORY_MAX_LOG_LEN_PREF = "devtools.performance.memory.max-log-length";
|
||||||
|
const PROFILER_BUFFER_SIZE_PREF = "devtools.performance.profiler.buffer-size";
|
||||||
|
const PROFILER_SAMPLE_RATE_PREF = "devtools.performance.profiler.sample-frequency-khz";
|
||||||
|
|
||||||
const FRAMERATE_PREF = "devtools.performance.ui.enable-framerate";
|
const FRAMERATE_PREF = "devtools.performance.ui.enable-framerate";
|
||||||
const MEMORY_PREF = "devtools.performance.ui.enable-memory";
|
const MEMORY_PREF = "devtools.performance.ui.enable-memory";
|
||||||
@ -50,8 +52,12 @@ let DEFAULT_PREFS = [
|
|||||||
"devtools.performance.ui.enable-memory",
|
"devtools.performance.ui.enable-memory",
|
||||||
"devtools.performance.ui.enable-framerate",
|
"devtools.performance.ui.enable-framerate",
|
||||||
"devtools.performance.ui.show-jit-optimizations",
|
"devtools.performance.ui.show-jit-optimizations",
|
||||||
|
"devtools.performance.memory.sample-probability",
|
||||||
|
"devtools.performance.memory.max-log-length",
|
||||||
|
"devtools.performance.profiler.buffer-size",
|
||||||
|
"devtools.performance.profiler.sample-frequency-khz",
|
||||||
].reduce((prefs, pref) => {
|
].reduce((prefs, pref) => {
|
||||||
prefs[pref] = Services.prefs.getBoolPref(pref);
|
prefs[pref] = Preferences.get(pref);
|
||||||
return prefs;
|
return prefs;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
@ -78,7 +84,7 @@ registerCleanupFunction(() => {
|
|||||||
|
|
||||||
// Rollback any pref changes
|
// Rollback any pref changes
|
||||||
Object.keys(DEFAULT_PREFS).forEach(pref => {
|
Object.keys(DEFAULT_PREFS).forEach(pref => {
|
||||||
Services.prefs.setBoolPref(pref, DEFAULT_PREFS[pref]);
|
Preferences.set(pref, DEFAULT_PREFS[pref]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure the profiler module is stopped when the test finishes.
|
// Make sure the profiler module is stopped when the test finishes.
|
||||||
@ -473,11 +479,6 @@ function dropSelection(graph) {
|
|||||||
graph.emit("selecting");
|
graph.emit("selecting");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSourceActor(aSources, aURL) {
|
|
||||||
let item = aSources.getItemForAttachment(a => a.source.url === aURL);
|
|
||||||
return item && item.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires a key event, like "VK_UP", "VK_DOWN", etc.
|
* Fires a key event, like "VK_UP", "VK_DOWN", etc.
|
||||||
*/
|
*/
|
||||||
|
@ -61,9 +61,13 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
|||||||
*/
|
*/
|
||||||
_onLink: function (_, treeItem) {
|
_onLink: function (_, treeItem) {
|
||||||
let { url, line } = treeItem.frame.getInfo();
|
let { url, line } = treeItem.frame.getInfo();
|
||||||
viewSourceInDebugger(url, line).then(
|
gToolbox.viewSourceInDebugger(url, line).then(success => {
|
||||||
() => this.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER),
|
if (success) {
|
||||||
() => this.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER));
|
this.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
|
||||||
|
} else {
|
||||||
|
this.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,31 +125,3 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
|||||||
|
|
||||||
toString: () => "[object JsCallTreeView]"
|
toString: () => "[object JsCallTreeView]"
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens/selects the debugger in this toolbox and jumps to the specified
|
|
||||||
* file name and line number.
|
|
||||||
* @param string url
|
|
||||||
* @param number line
|
|
||||||
*/
|
|
||||||
let viewSourceInDebugger = Task.async(function *(url, line) {
|
|
||||||
// If the Debugger was already open, switch to it and try to show the
|
|
||||||
// source immediately. Otherwise, initialize it and wait for the sources
|
|
||||||
// to be added first.
|
|
||||||
let debuggerAlreadyOpen = gToolbox.getPanel("jsdebugger");
|
|
||||||
let { panelWin: dbg } = yield gToolbox.selectTool("jsdebugger");
|
|
||||||
|
|
||||||
if (!debuggerAlreadyOpen) {
|
|
||||||
yield dbg.once(dbg.EVENTS.SOURCES_ADDED);
|
|
||||||
}
|
|
||||||
|
|
||||||
let { DebuggerView } = dbg;
|
|
||||||
let { Sources } = DebuggerView;
|
|
||||||
|
|
||||||
let item = Sources.getItemForAttachment(a => a.source.url === url);
|
|
||||||
if (item) {
|
|
||||||
return DebuggerView.setEditorLocation(item.attachment.source.actor, line, { noDebug: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject("Couldn't find the specified source in the debugger.");
|
|
||||||
});
|
|
||||||
|
@ -51,9 +51,13 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
|
|||||||
*/
|
*/
|
||||||
_onLink: function (_, treeItem) {
|
_onLink: function (_, treeItem) {
|
||||||
let { url, line } = treeItem.frame.getInfo();
|
let { url, line } = treeItem.frame.getInfo();
|
||||||
viewSourceInDebugger(url, line).then(
|
gToolbox.viewSourceInDebugger(url, line).then(success => {
|
||||||
() => this.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER),
|
if (success) {
|
||||||
() => this.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER));
|
this.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
|
||||||
|
} else {
|
||||||
|
this.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,10 +29,12 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
|||||||
|
|
||||||
this._onMarkerSelected = this._onMarkerSelected.bind(this);
|
this._onMarkerSelected = this._onMarkerSelected.bind(this);
|
||||||
this._onResize = this._onResize.bind(this);
|
this._onResize = this._onResize.bind(this);
|
||||||
|
this._onViewSource = this._onViewSource.bind(this);
|
||||||
|
|
||||||
this.waterfall.on("selected", this._onMarkerSelected);
|
this.waterfall.on("selected", this._onMarkerSelected);
|
||||||
this.waterfall.on("unselected", this._onMarkerSelected);
|
this.waterfall.on("unselected", this._onMarkerSelected);
|
||||||
this.details.on("resize", this._onResize);
|
this.details.on("resize", this._onResize);
|
||||||
|
this.details.on("view-source", this._onViewSource);
|
||||||
|
|
||||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||||
this.waterfall.setBlueprint(blueprint);
|
this.waterfall.setBlueprint(blueprint);
|
||||||
@ -48,6 +50,7 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
|||||||
this.waterfall.off("selected", this._onMarkerSelected);
|
this.waterfall.off("selected", this._onMarkerSelected);
|
||||||
this.waterfall.off("unselected", this._onMarkerSelected);
|
this.waterfall.off("unselected", this._onMarkerSelected);
|
||||||
this.details.off("resize", this._onResize);
|
this.details.off("resize", this._onResize);
|
||||||
|
this.details.off("view-source", this._onViewSource);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,5 +100,12 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
|||||||
this.waterfall.setBlueprint(blueprint);
|
this.waterfall.setBlueprint(blueprint);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when MarkerDetails view emits an event to view source.
|
||||||
|
*/
|
||||||
|
_onViewSource: function (_, file, line) {
|
||||||
|
gToolbox.viewSourceInDebugger(file, line);
|
||||||
|
},
|
||||||
|
|
||||||
toString: () => "[object WaterfallView]"
|
toString: () => "[object WaterfallView]"
|
||||||
});
|
});
|
||||||
|
@ -350,7 +350,7 @@ let JITOptimizationsView = {
|
|||||||
fileName = url.slice(url.lastIndexOf("/") + 1);
|
fileName = url.slice(url.lastIndexOf("/") + 1);
|
||||||
node.classList.add("debugger-link");
|
node.classList.add("debugger-link");
|
||||||
node.setAttribute("tooltiptext", URL_LABEL_TOOLTIP + " → " + url);
|
node.setAttribute("tooltiptext", URL_LABEL_TOOLTIP + " → " + url);
|
||||||
node.addEventListener("click", () => viewSourceInDebugger(url, line));
|
node.addEventListener("click", () => gToolbox.viewSourceInDebugger(url, line));
|
||||||
}
|
}
|
||||||
fileName = fileName || url || "";
|
fileName = fileName || url || "";
|
||||||
node.textContent = fileName ? `@${fileName}` : "";
|
node.textContent = fileName ? `@${fileName}` : "";
|
||||||
|
@ -22,6 +22,14 @@ var RenamePlugin = Class({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onContextMenuOpen: function(resource) {
|
||||||
|
if (resource.isRoot) {
|
||||||
|
this.contextMenuItem.setAttribute("hidden", "true");
|
||||||
|
} else {
|
||||||
|
this.contextMenuItem.removeAttribute("hidden");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onCommand: function(cmd) {
|
onCommand: function(cmd) {
|
||||||
if (cmd === "cmd-rename") {
|
if (cmd === "cmd-rename") {
|
||||||
let tree = this.host.projectTree;
|
let tree = this.host.projectTree;
|
||||||
|
@ -54,6 +54,7 @@ EXTRA_JS_MODULES.devtools.shared += [
|
|||||||
'inplace-editor.js',
|
'inplace-editor.js',
|
||||||
'observable-object.js',
|
'observable-object.js',
|
||||||
'options-view.js',
|
'options-view.js',
|
||||||
|
'source-utils.js',
|
||||||
'telemetry.js',
|
'telemetry.js',
|
||||||
'theme-switching.js',
|
'theme-switching.js',
|
||||||
'theme.js',
|
'theme.js',
|
||||||
@ -66,6 +67,7 @@ EXTRA_JS_MODULES.devtools.shared.widgets += [
|
|||||||
'widgets/FastListWidget.js',
|
'widgets/FastListWidget.js',
|
||||||
'widgets/FilterWidget.js',
|
'widgets/FilterWidget.js',
|
||||||
'widgets/FlameGraph.js',
|
'widgets/FlameGraph.js',
|
||||||
|
'widgets/MdnDocsWidget.js',
|
||||||
'widgets/Spectrum.js',
|
'widgets/Spectrum.js',
|
||||||
'widgets/TableWidget.js',
|
'widgets/TableWidget.js',
|
||||||
'widgets/Tooltip.js',
|
'widgets/Tooltip.js',
|
||||||
|
128
browser/devtools/shared/source-utils.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/* 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";
|
||||||
|
|
||||||
|
loader.lazyRequireGetter(this, "Services");
|
||||||
|
loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
|
||||||
|
loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to open a Stylesheet file in the Style Editor. If the file is not found,
|
||||||
|
* it is opened in source view instead.
|
||||||
|
* Returns a promise resolving to a boolean indicating whether or not
|
||||||
|
* the source was able to be displayed in the StyleEditor, as the built-in Firefox
|
||||||
|
* View Source is the fallback.
|
||||||
|
*
|
||||||
|
* @param {Toolbox} toolbox
|
||||||
|
* @param {string} sourceURL
|
||||||
|
* @param {number} sourceLine
|
||||||
|
*
|
||||||
|
* @return {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
exports.viewSourceInStyleEditor = Task.async(function *(toolbox, sourceURL, sourceLine) {
|
||||||
|
let panel = yield toolbox.loadTool("styleeditor");
|
||||||
|
|
||||||
|
try {
|
||||||
|
let selected = panel.UI.once("editor-selected");
|
||||||
|
yield panel.selectStyleSheet(sourceURL, sourceLine);
|
||||||
|
yield toolbox.selectTool("styleeditor");
|
||||||
|
yield selected;
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
exports.viewSource(toolbox, sourceURL, sourceLine);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to open a JavaScript file in the Debugger. If the file is not found,
|
||||||
|
* it is opened in source view instead.
|
||||||
|
* Returns a promise resolving to a boolean indicating whether or not
|
||||||
|
* the source was able to be displayed in the Debugger, as the built-in Firefox
|
||||||
|
* View Source is the fallback.
|
||||||
|
*
|
||||||
|
* @param {Toolbox} toolbox
|
||||||
|
* @param {string} sourceURL
|
||||||
|
* @param {number} sourceLine
|
||||||
|
*
|
||||||
|
* @return {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
exports.viewSourceInDebugger = Task.async(function *(toolbox, sourceURL, sourceLine) {
|
||||||
|
// If the Debugger was already open, switch to it and try to show the
|
||||||
|
// source immediately. Otherwise, initialize it and wait for the sources
|
||||||
|
// to be added first.
|
||||||
|
let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger");
|
||||||
|
let { panelWin: dbg } = yield toolbox.loadTool("jsdebugger");
|
||||||
|
|
||||||
|
if (!debuggerAlreadyOpen) {
|
||||||
|
yield dbg.once(dbg.EVENTS.SOURCES_ADDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
let { DebuggerView } = dbg;
|
||||||
|
let { Sources } = DebuggerView;
|
||||||
|
|
||||||
|
let item = Sources.getItemForAttachment(a => a.source.url === sourceURL);
|
||||||
|
if (item) {
|
||||||
|
yield toolbox.selectTool("jsdebugger");
|
||||||
|
yield DebuggerView.setEditorLocation(item.attachment.source.actor, sourceLine, { noDebug: true });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not found, still attempt to open in View Source
|
||||||
|
exports.viewSource(toolbox, sourceURL, sourceLine);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to open a JavaScript file in the corresponding Scratchpad.
|
||||||
|
*
|
||||||
|
* @param {string} sourceURL
|
||||||
|
* @param {number} sourceLine
|
||||||
|
*
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
exports.viewSourceInScratchpad = Task.async(function *(sourceURL, sourceLine) {
|
||||||
|
// Check for matching top level scratchpad window.
|
||||||
|
let wins = Services.wm.getEnumerator("devtools:scratchpad");
|
||||||
|
|
||||||
|
while (wins.hasMoreElements()) {
|
||||||
|
let win = wins.getNext();
|
||||||
|
|
||||||
|
if (!win.closed && win.Scratchpad.uniqueName === sourceURL) {
|
||||||
|
win.focus();
|
||||||
|
win.Scratchpad.editor.setCursor({ line: sourceLine, ch: 0 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For scratchpads within toolbox
|
||||||
|
for (let [, toolbox] of gDevTools) {
|
||||||
|
let scratchpadPanel = toolbox.getPanel("scratchpad");
|
||||||
|
if (scratchpadPanel) {
|
||||||
|
let { scratchpad } = scratchpadPanel;
|
||||||
|
if (scratchpad.uniqueName === sourceURL) {
|
||||||
|
toolbox.selectTool("scratchpad");
|
||||||
|
toolbox.raise();
|
||||||
|
scratchpad.editor.focus();
|
||||||
|
scratchpad.editor.setCursor({ line: sourceLine, ch: 0 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a link in Firefox's View Source.
|
||||||
|
*
|
||||||
|
* @param {Toolbox} toolbox
|
||||||
|
* @param {string} sourceURL
|
||||||
|
* @param {number} sourceLine
|
||||||
|
*
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
exports.viewSource = Task.async(function *(toolbox, sourceURL, sourceLine) {
|
||||||
|
let utils = toolbox.gViewSourceUtils;
|
||||||
|
utils.viewSource(sourceURL, null, toolbox.doc, sourceLine || 0);
|
||||||
|
});
|
@ -11,6 +11,11 @@ support-files =
|
|||||||
browser_devices.json
|
browser_devices.json
|
||||||
doc_options-view.xul
|
doc_options-view.xul
|
||||||
head.js
|
head.js
|
||||||
|
html-mdn-css-basic-testing.html
|
||||||
|
html-mdn-css-no-summary.html
|
||||||
|
html-mdn-css-no-summary-or-syntax.html
|
||||||
|
html-mdn-css-no-syntax.html
|
||||||
|
html-mdn-css-syntax-old-style.html
|
||||||
leakhunt.js
|
leakhunt.js
|
||||||
|
|
||||||
[browser_css_color.js]
|
[browser_css_color.js]
|
||||||
@ -70,6 +75,8 @@ support-files =
|
|||||||
skip-if = e10s # Layouthelpers test should not run in a content page.
|
skip-if = e10s # Layouthelpers test should not run in a content page.
|
||||||
[browser_layoutHelpers-getBoxQuads.js]
|
[browser_layoutHelpers-getBoxQuads.js]
|
||||||
skip-if = e10s # Layouthelpers test should not run in a content page.
|
skip-if = e10s # Layouthelpers test should not run in a content page.
|
||||||
|
[browser_mdn-docs-01.js]
|
||||||
|
[browser_mdn-docs-02.js]
|
||||||
[browser_num-l10n.js]
|
[browser_num-l10n.js]
|
||||||
[browser_observableobject.js]
|
[browser_observableobject.js]
|
||||||
[browser_options-view-01.js]
|
[browser_options-view-01.js]
|
||||||
|
174
browser/devtools/shared/test/browser_mdn-docs-01.js
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
/* vim: set ts=2 et sw=2 tw=80: */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file tests the MdnDocsWidget object, and specifically its
|
||||||
|
* loadCssDocs() function.
|
||||||
|
*
|
||||||
|
* The MdnDocsWidget is initialized with a document which has a specific
|
||||||
|
* structure. You then call loadCssDocs(), passing in a CSS property name.
|
||||||
|
* MdnDocsWidget then fetches docs for that property by making an XHR to
|
||||||
|
* a docs page, and loads the results into the document. While the XHR is
|
||||||
|
* still not resolved the document is put into an "initializing" state in
|
||||||
|
* which the devtools throbber is displayed.
|
||||||
|
*
|
||||||
|
* In this file we test:
|
||||||
|
* - the initial state of the document before the docs have loaded
|
||||||
|
* - the state of the document after the docs have loaded
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {CssDocsTooltip} = require("devtools/shared/widgets/Tooltip");
|
||||||
|
const {setBaseCssDocsUrl, MdnDocsWidget} = devtools.require("devtools/shared/widgets/MdnDocsWidget");
|
||||||
|
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||||
|
|
||||||
|
// frame to load the tooltip into
|
||||||
|
const MDN_DOCS_TOOLTIP_FRAME = "chrome://browser/content/devtools/mdn-docs-frame.xhtml";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test properties
|
||||||
|
*
|
||||||
|
* In the real tooltip, a CSS property name is used to look up an MDN page
|
||||||
|
* for that property.
|
||||||
|
* In the test code, the names defined here is used to look up a page
|
||||||
|
* served by the test server.
|
||||||
|
*/
|
||||||
|
const BASIC_TESTING_PROPERTY = "html-mdn-css-basic-testing.html";
|
||||||
|
|
||||||
|
const BASIC_EXPECTED_SUMMARY = "A summary of the property.";
|
||||||
|
const BASIC_EXPECTED_SYNTAX = "/* The part we want */\nthis: is-the-part-we-want";
|
||||||
|
|
||||||
|
const URI_PARAMS = "?utm_source=mozilla&utm_medium=firefox-inspector&utm_campaign=default";
|
||||||
|
|
||||||
|
add_task(function*() {
|
||||||
|
setBaseCssDocsUrl(TEST_URI_ROOT);
|
||||||
|
|
||||||
|
yield promiseTab("about:blank");
|
||||||
|
let [host, win, doc] = yield createHost("bottom", MDN_DOCS_TOOLTIP_FRAME);
|
||||||
|
let widget = new MdnDocsWidget(win.document);
|
||||||
|
|
||||||
|
yield testTheBasics(widget);
|
||||||
|
|
||||||
|
host.destroy();
|
||||||
|
gBrowser.removeCurrentTab();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test all the basics
|
||||||
|
* - initial content, before docs have loaded, is as expected
|
||||||
|
* - throbber is set before docs have loaded
|
||||||
|
* - contents are as expected after docs have loaded
|
||||||
|
* - throbber is gone after docs have loaded
|
||||||
|
* - mdn link text is correct and onclick behavior is correct
|
||||||
|
*/
|
||||||
|
function* testTheBasics(widget) {
|
||||||
|
info("Test all the basic functionality in the widget");
|
||||||
|
|
||||||
|
info("Get the widget state before docs have loaded");
|
||||||
|
let promise = widget.loadCssDocs(BASIC_TESTING_PROPERTY);
|
||||||
|
|
||||||
|
info("Check initial contents before docs have loaded");
|
||||||
|
checkTooltipContents(widget.elements, {
|
||||||
|
propertyName: BASIC_TESTING_PROPERTY,
|
||||||
|
summary: "",
|
||||||
|
syntax: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
// throbber is set
|
||||||
|
ok(widget.elements.info.classList.contains("devtools-throbber"),
|
||||||
|
"Throbber is set");
|
||||||
|
|
||||||
|
info("Now let the widget finish loading");
|
||||||
|
yield promise;
|
||||||
|
|
||||||
|
info("Check contents after docs have loaded");
|
||||||
|
checkTooltipContents(widget.elements, {
|
||||||
|
propertyName: BASIC_TESTING_PROPERTY,
|
||||||
|
summary: BASIC_EXPECTED_SUMMARY,
|
||||||
|
syntax: BASIC_EXPECTED_SYNTAX
|
||||||
|
});
|
||||||
|
|
||||||
|
// throbber is gone
|
||||||
|
ok(!widget.elements.info.classList.contains("devtools-throbber"),
|
||||||
|
"Throbber is not set");
|
||||||
|
|
||||||
|
info("Check that MDN link text is correct and onclick behavior is correct");
|
||||||
|
|
||||||
|
let mdnLink = widget.elements.linkToMdn;
|
||||||
|
let expectedHref = TEST_URI_ROOT + BASIC_TESTING_PROPERTY + URI_PARAMS;
|
||||||
|
is(mdnLink.href, expectedHref, "MDN link href is correct");
|
||||||
|
|
||||||
|
let uri = yield checkLinkClick(mdnLink);
|
||||||
|
is(uri, expectedHref, "New tab opened with the expected URI");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clicking the "Visit MDN Page" in the tooltip panel
|
||||||
|
* should open a new browser tab with the page loaded.
|
||||||
|
*
|
||||||
|
* To test this we'll listen for a new tab opening, and
|
||||||
|
* when it does, add a listener to that new tab to tell
|
||||||
|
* us when it has loaded.
|
||||||
|
*
|
||||||
|
* Then we click the link.
|
||||||
|
*
|
||||||
|
* In the tab's load listener, we'll resolve the promise
|
||||||
|
* with the URI, which is expected to match the href
|
||||||
|
* in the orginal link.
|
||||||
|
*
|
||||||
|
* One complexity is that when you open a new tab,
|
||||||
|
* "about:blank" is first loaded into the tab before the
|
||||||
|
* actual page. So we ignore that first load event, and keep
|
||||||
|
* listening until "load" is triggered for a different URI.
|
||||||
|
*/
|
||||||
|
function checkLinkClick(link) {
|
||||||
|
|
||||||
|
function loadListener(e) {
|
||||||
|
let tab = e.target;
|
||||||
|
var browser = getBrowser().getBrowserForTab(tab);
|
||||||
|
var uri = browser.currentURI.spec;
|
||||||
|
// this is horrible, and it's because when we open a new tab
|
||||||
|
// "about:blank: is first loaded into it, before the actual
|
||||||
|
// document we want to load.
|
||||||
|
if (uri != "about:blank") {
|
||||||
|
info("New browser tab has loaded");
|
||||||
|
tab.removeEventListener("load", loadListener);
|
||||||
|
gBrowser.removeTab(tab);
|
||||||
|
info("Resolve promise with new tab URI");
|
||||||
|
deferred.resolve(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function newTabListener(e) {
|
||||||
|
gBrowser.tabContainer.removeEventListener("TabOpen", newTabListener);
|
||||||
|
var tab = e.target;
|
||||||
|
tab.addEventListener("load", loadListener, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let deferred = promise.defer();
|
||||||
|
info("Check that clicking the link opens a new tab with the correct URI");
|
||||||
|
gBrowser.tabContainer.addEventListener("TabOpen", newTabListener, false);
|
||||||
|
info("Click the link to MDN");
|
||||||
|
link.click();
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to check content of the tooltip.
|
||||||
|
*/
|
||||||
|
function checkTooltipContents(doc, expected) {
|
||||||
|
|
||||||
|
is(doc.heading.textContent,
|
||||||
|
expected.propertyName,
|
||||||
|
"Property name is correct");
|
||||||
|
|
||||||
|
is(doc.summary.textContent,
|
||||||
|
expected.summary,
|
||||||
|
"Summary is correct");
|
||||||
|
|
||||||
|
is(doc.syntax.textContent,
|
||||||
|
expected.syntax,
|
||||||
|
"Syntax is correct");
|
||||||
|
}
|
134
browser/devtools/shared/test/browser_mdn-docs-02.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/* vim: set ts=2 et sw=2 tw=80: */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file tests the MdnDocsWidget object, and specifically its
|
||||||
|
* loadCssDocs() function.
|
||||||
|
*
|
||||||
|
* The MdnDocsWidget is initialized with a document which has a specific
|
||||||
|
* structure. You then call loadCssDocs(), passing in a CSS property name.
|
||||||
|
* MdnDocsWidget then fetches docs for that property by making an XHR to
|
||||||
|
* a docs page, and loads the results into the document.
|
||||||
|
*
|
||||||
|
* In this file we test that the tooltip can properly handle the different
|
||||||
|
* structures that the docs page might have, including variant structures and
|
||||||
|
* error conditions like parts of the document being missing.
|
||||||
|
*
|
||||||
|
* We also test that the tooltip properly handles the case where the page
|
||||||
|
* doesn't exist at all.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {CssDocsTooltip} = require("devtools/shared/widgets/Tooltip");
|
||||||
|
const {setBaseCssDocsUrl, MdnDocsWidget} = devtools.require("devtools/shared/widgets/MdnDocsWidget");
|
||||||
|
|
||||||
|
// frame to load the tooltip into
|
||||||
|
const MDN_DOCS_TOOLTIP_FRAME = "chrome://browser/content/devtools/mdn-docs-frame.xhtml";
|
||||||
|
|
||||||
|
const BASIC_EXPECTED_SUMMARY = "A summary of the property.";
|
||||||
|
const BASIC_EXPECTED_SYNTAX = "/* The part we want */\nthis: is-the-part-we-want";
|
||||||
|
const ERROR_MESSAGE = "Could not load docs page.";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test properties
|
||||||
|
*
|
||||||
|
* In the real tooltip, a CSS property name is used to look up an MDN page
|
||||||
|
* for that property.
|
||||||
|
* In the test code, the names defined here are used to look up a page
|
||||||
|
* served by the test server. We have different properties to test
|
||||||
|
* different ways that the docs pages might be constructed, including errors
|
||||||
|
* like pages that don't include docs where we expect.
|
||||||
|
*/
|
||||||
|
const SYNTAX_OLD_STYLE = "html-mdn-css-syntax-old-style.html";
|
||||||
|
const NO_SUMMARY = "html-mdn-css-no-summary.html";
|
||||||
|
const NO_SYNTAX = "html-mdn-css-no-syntax.html";
|
||||||
|
const NO_SUMMARY_OR_SYNTAX = "html-mdn-css-no-summary-or-syntax.html";
|
||||||
|
|
||||||
|
const TEST_DATA = [{
|
||||||
|
desc: "Test a property for which we don't have a page",
|
||||||
|
docsPageUrl: "i-dont-exist.html",
|
||||||
|
expectedContents: {
|
||||||
|
propertyName: "i-dont-exist.html",
|
||||||
|
summary: ERROR_MESSAGE,
|
||||||
|
syntax: ""
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
desc: "Test a property whose syntax section is specified using an old-style page",
|
||||||
|
docsPageUrl: SYNTAX_OLD_STYLE,
|
||||||
|
expectedContents: {
|
||||||
|
propertyName: SYNTAX_OLD_STYLE,
|
||||||
|
summary: BASIC_EXPECTED_SUMMARY,
|
||||||
|
syntax: BASIC_EXPECTED_SYNTAX
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
desc: "Test a property whose page doesn't have a summary",
|
||||||
|
docsPageUrl: NO_SUMMARY,
|
||||||
|
expectedContents: {
|
||||||
|
propertyName: NO_SUMMARY,
|
||||||
|
summary: "",
|
||||||
|
syntax: BASIC_EXPECTED_SYNTAX
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
desc: "Test a property whose page doesn't have a syntax",
|
||||||
|
docsPageUrl: NO_SYNTAX,
|
||||||
|
expectedContents: {
|
||||||
|
propertyName: NO_SYNTAX,
|
||||||
|
summary: BASIC_EXPECTED_SUMMARY,
|
||||||
|
syntax: ""
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
desc: "Test a property whose page doesn't have a summary or a syntax",
|
||||||
|
docsPageUrl: NO_SUMMARY_OR_SYNTAX,
|
||||||
|
expectedContents: {
|
||||||
|
propertyName: NO_SUMMARY_OR_SYNTAX,
|
||||||
|
summary: ERROR_MESSAGE,
|
||||||
|
syntax: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
add_task(function*() {
|
||||||
|
setBaseCssDocsUrl(TEST_URI_ROOT);
|
||||||
|
|
||||||
|
yield promiseTab("about:blank");
|
||||||
|
let [host, win, doc] = yield createHost("bottom", MDN_DOCS_TOOLTIP_FRAME);
|
||||||
|
let widget = new MdnDocsWidget(win.document);
|
||||||
|
|
||||||
|
for (let {desc, docsPageUrl, expectedContents} of TEST_DATA) {
|
||||||
|
info(desc);
|
||||||
|
yield widget.loadCssDocs(docsPageUrl);
|
||||||
|
checkTooltipContents(widget.elements, expectedContents);
|
||||||
|
}
|
||||||
|
host.destroy();
|
||||||
|
gBrowser.removeCurrentTab();
|
||||||
|
});
|
||||||
|
|
||||||
|
function* testNonExistentPage(widget) {
|
||||||
|
info("Test a property for which we don't have a page");
|
||||||
|
yield widget.loadCssDocs("i-dont-exist.html");
|
||||||
|
checkTooltipContents(widget.elements, {
|
||||||
|
propertyName: "i-dont-exist.html",
|
||||||
|
summary: ERROR_MESSAGE,
|
||||||
|
syntax: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Utility function to check content of the tooltip.
|
||||||
|
*/
|
||||||
|
function checkTooltipContents(doc, expected) {
|
||||||
|
|
||||||
|
is(doc.heading.textContent,
|
||||||
|
expected.propertyName,
|
||||||
|
"Property name is correct");
|
||||||
|
|
||||||
|
is(doc.summary.textContent,
|
||||||
|
expected.summary,
|
||||||
|
"Summary is correct");
|
||||||
|
|
||||||
|
is(doc.syntax.textContent,
|
||||||
|
expected.syntax,
|
||||||
|
"Syntax is correct");
|
||||||
|
}
|
21
browser/devtools/shared/test/html-mdn-css-basic-testing.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h2 id="Summary">Summary</h2>
|
||||||
|
|
||||||
|
<p>A summary of the property.</p>
|
||||||
|
|
||||||
|
<h2 id="Syntax">Syntax</h2>
|
||||||
|
|
||||||
|
<pre>/* The part we want */
|
||||||
|
this: is-the-part-we-want</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<p>This is not the summary or the syntax.</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
21
browser/devtools/shared/test/html-mdn-css-no-summary.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<p>This is not the summary.</p>
|
||||||
|
|
||||||
|
<h2 id="Syntax">Syntax</h2>
|
||||||
|
|
||||||
|
<pre>To be ignored.</pre>
|
||||||
|
|
||||||
|
<pre>/* The part we want */
|
||||||
|
this: is-the-part-we-want</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
17
browser/devtools/shared/test/html-mdn-css-no-syntax.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h2 id="Summary">Summary</h2>
|
||||||
|
|
||||||
|
<p>A summary of the property.</p>
|
||||||
|
|
||||||
|
<p>This is not the syntax.</p>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h2 id="Summary">Summary</h2>
|
||||||
|
|
||||||
|
<p>A summary of the property.</p>
|
||||||
|
|
||||||
|
<h2 id="Syntax">Syntax</h2>
|
||||||
|
|
||||||
|
<pre>The part we should ignore</pre>
|
||||||
|
|
||||||
|
<pre>/* The part we want */
|
||||||
|
this: is-the-part-we-want</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -138,6 +138,9 @@ MarkerDetails.prototype = {
|
|||||||
case "DOMEvent":
|
case "DOMEvent":
|
||||||
this.renderDOMEventMarker(this._parent, marker);
|
this.renderDOMEventMarker(this._parent, marker);
|
||||||
break;
|
break;
|
||||||
|
case "Javascript":
|
||||||
|
this.renderJavascriptMarker(this._parent, marker);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,7 +222,7 @@ MarkerDetails.prototype = {
|
|||||||
|
|
||||||
aNode.addEventListener("click", (event) => {
|
aNode.addEventListener("click", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
viewSourceInDebugger(toolbox, url, line);
|
this.emit("view-source", url, line);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,36 +287,21 @@ MarkerDetails.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render details of a Javascript marker.
|
||||||
|
*
|
||||||
|
* @param nsIDOMNode parent
|
||||||
|
* The parent node holding the view.
|
||||||
|
* @param object marker
|
||||||
|
* The marker to display.
|
||||||
|
*/
|
||||||
|
renderJavascriptMarker: function(parent, marker) {
|
||||||
|
if ("causeName" in marker) {
|
||||||
|
let cause = this.buildNameValueLabel("timeline.markerDetail.causeName", marker.causeName);
|
||||||
|
this._parent.appendChild(cause);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens/selects the debugger in this toolbox and jumps to the specified
|
|
||||||
* file name and line number.
|
|
||||||
* @param object toolbox
|
|
||||||
* The toolbox.
|
|
||||||
* @param string url
|
|
||||||
* @param number line
|
|
||||||
*/
|
|
||||||
let viewSourceInDebugger = Task.async(function *(toolbox, url, line) {
|
|
||||||
// If the Debugger was already open, switch to it and try to show the
|
|
||||||
// source immediately. Otherwise, initialize it and wait for the sources
|
|
||||||
// to be added first.
|
|
||||||
let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger");
|
|
||||||
let { panelWin: dbg } = yield toolbox.selectTool("jsdebugger");
|
|
||||||
|
|
||||||
if (!debuggerAlreadyOpen) {
|
|
||||||
yield dbg.once(dbg.EVENTS.SOURCES_ADDED);
|
|
||||||
}
|
|
||||||
|
|
||||||
let { DebuggerView } = dbg;
|
|
||||||
let { Sources } = DebuggerView;
|
|
||||||
|
|
||||||
let item = Sources.getItemForAttachment(a => a.source.url === url);
|
|
||||||
if (item) {
|
|
||||||
return DebuggerView.setEditorLocation(item.attachment.source.actor, line, { noDebug: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject("Couldn't find the specified source in the debugger.");
|
|
||||||
});
|
|
||||||
|
|
||||||
exports.MarkerDetails = MarkerDetails;
|
exports.MarkerDetails = MarkerDetails;
|
||||||
|
409
browser/devtools/shared/widgets/MdnDocsWidget.js
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains functions to retrieve docs content from
|
||||||
|
* MDN (developer.mozilla.org) for particular items, and to display
|
||||||
|
* the content in a tooltip.
|
||||||
|
*
|
||||||
|
* At the moment it only supports fetching content for CSS properties,
|
||||||
|
* but it might support other types of content in the future
|
||||||
|
* (Web APIs, for example).
|
||||||
|
*
|
||||||
|
* It's split into two parts:
|
||||||
|
*
|
||||||
|
* - functions like getCssDocs that just fetch content from MDN,
|
||||||
|
* without any constraints on what to do with the content. If you
|
||||||
|
* want to embed the content in some custom way, use this.
|
||||||
|
*
|
||||||
|
* - the MdnDocsWidget class, that manages and updates a tooltip
|
||||||
|
* document whose content is taken from MDN. If you want to embed
|
||||||
|
* the content in a tooltip, use this in conjunction with Tooltip.js.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {Cc, Cu, Ci} = require("chrome");
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/Promise.jsm");
|
||||||
|
|
||||||
|
// Parameters for the XHR request
|
||||||
|
// see https://developer.mozilla.org/en-US/docs/MDN/Kuma/API#Document_parameters
|
||||||
|
const XHR_PARAMS = "?raw¯os";
|
||||||
|
// URL for the XHR request
|
||||||
|
var XHR_CSS_URL = "https://developer.mozilla.org/en-US/docs/Web/CSS/";
|
||||||
|
|
||||||
|
// Parameters for the link to MDN in the tooltip, so
|
||||||
|
// so we know which MDN visits come from this feature
|
||||||
|
const PAGE_LINK_PARAMS = "?utm_source=mozilla&utm_medium=firefox-inspector&utm_campaign=default"
|
||||||
|
// URL for the page link
|
||||||
|
// omits locale, so a locale-specific page will be loaded
|
||||||
|
var PAGE_LINK_URL = "https://developer.mozilla.org/docs/Web/CSS/";
|
||||||
|
|
||||||
|
const BROWSER_WINDOW = 'navigator:browser';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch an MDN page.
|
||||||
|
*
|
||||||
|
* @param {string} pageUrl
|
||||||
|
* URL of the page to fetch.
|
||||||
|
*
|
||||||
|
* @return {promise}
|
||||||
|
* The promise is resolved with the page as an XML document.
|
||||||
|
*
|
||||||
|
* The promise is rejected with an error message if
|
||||||
|
* we could not load the page.
|
||||||
|
*/
|
||||||
|
function getMdnPage(pageUrl) {
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
|
||||||
|
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
|
||||||
|
|
||||||
|
xhr.addEventListener("load", onLoaded, false);
|
||||||
|
xhr.addEventListener("error", onError, false);
|
||||||
|
|
||||||
|
xhr.open("GET", pageUrl);
|
||||||
|
xhr.responseType = "document";
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
function onLoaded(e) {
|
||||||
|
if (xhr.status != 200) {
|
||||||
|
deferred.reject({page: pageUrl, status: xhr.status});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
deferred.resolve(xhr.responseXML);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError(e) {
|
||||||
|
deferred.reject({page: pageUrl, status: xhr.status});
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets some docs for the given CSS property.
|
||||||
|
* Loads an MDN page for the property and gets some
|
||||||
|
* information about the property.
|
||||||
|
*
|
||||||
|
* @param {string} cssProperty
|
||||||
|
* The property for which we want docs.
|
||||||
|
*
|
||||||
|
* @return {promise}
|
||||||
|
* The promise is resolved with an object containing:
|
||||||
|
* - summary: a short summary of the property
|
||||||
|
* - syntax: some example syntax
|
||||||
|
*
|
||||||
|
* The promise is rejected with an error message if
|
||||||
|
* we could not load the page.
|
||||||
|
*/
|
||||||
|
function getCssDocs(cssProperty) {
|
||||||
|
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
let pageUrl = XHR_CSS_URL + cssProperty + XHR_PARAMS;
|
||||||
|
|
||||||
|
getMdnPage(pageUrl).then(parseDocsFromResponse, handleRejection);
|
||||||
|
|
||||||
|
function parseDocsFromResponse(responseDocument) {
|
||||||
|
let theDocs = {};
|
||||||
|
theDocs.summary = getSummary(responseDocument);
|
||||||
|
theDocs.syntax = getSyntax(responseDocument);
|
||||||
|
if (theDocs.summary || theDocs.syntax) {
|
||||||
|
deferred.resolve(theDocs);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
deferred.reject("Couldn't find the docs in the page.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRejection(e) {
|
||||||
|
deferred.reject(e.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getCssDocs = getCssDocs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MdnDocsWidget is used by tooltip code that needs to display docs
|
||||||
|
* from MDN in a tooltip. The tooltip code loads a document that contains the
|
||||||
|
* basic structure of a docs tooltip (loaded from mdn-docs-frame.xhtml),
|
||||||
|
* and passes this document into the widget's constructor.
|
||||||
|
*
|
||||||
|
* In the constructor, the widget does some general setup that's not
|
||||||
|
* dependent on the particular item we need docs for.
|
||||||
|
*
|
||||||
|
* After that, when the tooltip code needs to display docs for an item, it
|
||||||
|
* asks the widget to retrieve the docs and update the document with them.
|
||||||
|
*
|
||||||
|
* @param {Document} tooltipDocument
|
||||||
|
* A DOM document. The widget expects the document to have a particular
|
||||||
|
* structure.
|
||||||
|
*/
|
||||||
|
function MdnDocsWidget(tooltipDocument) {
|
||||||
|
|
||||||
|
// fetch all the bits of the document that we will manipulate later
|
||||||
|
this.elements = {
|
||||||
|
heading: tooltipDocument.getElementById("property-name"),
|
||||||
|
summary: tooltipDocument.getElementById("summary"),
|
||||||
|
syntax: tooltipDocument.getElementById("syntax"),
|
||||||
|
info: tooltipDocument.getElementById("property-info"),
|
||||||
|
linkToMdn: tooltipDocument.getElementById("visit-mdn-page")
|
||||||
|
};
|
||||||
|
|
||||||
|
// get the localized string for the link text
|
||||||
|
this.elements.linkToMdn.textContent =
|
||||||
|
l10n.strings.GetStringFromName("docsTooltip.visitMDN");
|
||||||
|
|
||||||
|
// listen for clicks and open in the browser window instead
|
||||||
|
let browserWindow = Services.wm.getMostRecentWindow(BROWSER_WINDOW);
|
||||||
|
this.elements.linkToMdn.addEventListener("click", function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
let link = e.target.href;
|
||||||
|
browserWindow.gBrowser.addTab(link);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.MdnDocsWidget = MdnDocsWidget;
|
||||||
|
|
||||||
|
MdnDocsWidget.prototype = {
|
||||||
|
/**
|
||||||
|
* This is called just before the tooltip is displayed, and is
|
||||||
|
* passed the CSS property for which we want to display help.
|
||||||
|
*
|
||||||
|
* Its job is to make sure the document contains the docs
|
||||||
|
* content for that CSS property.
|
||||||
|
*
|
||||||
|
* First, it initializes the document, setting the things it can
|
||||||
|
* set synchronously, resetting the things it needs to get
|
||||||
|
* asynchronously, and making sure the throbber is throbbing.
|
||||||
|
*
|
||||||
|
* Then it tries to get the content asynchronously, updating
|
||||||
|
* the document with the content or with an error message.
|
||||||
|
*
|
||||||
|
* It returns immediately, so the caller can display the tooltip
|
||||||
|
* without waiting for the asynch operation to complete.
|
||||||
|
*
|
||||||
|
* @param {string} propertyName
|
||||||
|
* The name of the CSS property for which we need to display help.
|
||||||
|
*/
|
||||||
|
loadCssDocs: function(propertyName) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do all the setup we can do synchronously, and get the document in
|
||||||
|
* a state where it can be displayed while we are waiting for the
|
||||||
|
* MDN docs content to be retrieved.
|
||||||
|
*/
|
||||||
|
function initializeDocument(propertyName) {
|
||||||
|
|
||||||
|
// set property name heading
|
||||||
|
elements.heading.textContent = propertyName;
|
||||||
|
|
||||||
|
// set link target
|
||||||
|
elements.linkToMdn.setAttribute("href",
|
||||||
|
PAGE_LINK_URL + propertyName + PAGE_LINK_PARAMS);
|
||||||
|
|
||||||
|
// clear docs summary and syntax
|
||||||
|
elements.summary.textContent = "";
|
||||||
|
elements.syntax.textContent = "";
|
||||||
|
|
||||||
|
// reset the scroll position
|
||||||
|
elements.info.scrollTop = 0;
|
||||||
|
elements.info.scrollLeft = 0;
|
||||||
|
|
||||||
|
// show the throbber
|
||||||
|
elements.info.classList.add("devtools-throbber");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called if we successfully got the docs content.
|
||||||
|
* Finishes setting up the tooltip content, and disables the throbber.
|
||||||
|
*/
|
||||||
|
function finalizeDocument({summary, syntax}) {
|
||||||
|
// set docs summary and syntax
|
||||||
|
elements.summary.textContent = summary;
|
||||||
|
elements.syntax.textContent = syntax;
|
||||||
|
|
||||||
|
// hide the throbber
|
||||||
|
elements.info.classList.remove("devtools-throbber");
|
||||||
|
|
||||||
|
deferred.resolve(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called if we failed to get the docs content.
|
||||||
|
* Sets the content to contain an error message, and disables the throbber.
|
||||||
|
*/
|
||||||
|
function gotError(error) {
|
||||||
|
// show error message
|
||||||
|
elements.summary.textContent = l10n.strings.GetStringFromName("docsTooltip.loadDocsError");
|
||||||
|
|
||||||
|
// hide the throbber
|
||||||
|
elements.info.classList.remove("devtools-throbber");
|
||||||
|
|
||||||
|
// although gotError is called when there's an error, we have handled
|
||||||
|
// the error, so call resolve not reject.
|
||||||
|
deferred.resolve(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
let deferred = Promise.defer();
|
||||||
|
let elements = this.elements;
|
||||||
|
|
||||||
|
initializeDocument(propertyName);
|
||||||
|
getCssDocs(propertyName).then(finalizeDocument, gotError);
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L10N utility class
|
||||||
|
*/
|
||||||
|
function L10N() {}
|
||||||
|
L10N.prototype = {};
|
||||||
|
|
||||||
|
let l10n = new L10N();
|
||||||
|
|
||||||
|
loader.lazyGetter(L10N.prototype, "strings", () => {
|
||||||
|
return Services.strings.createBundle(
|
||||||
|
"chrome://browser/locale/devtools/inspector.properties");
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether a node is all whitespace.
|
||||||
|
*
|
||||||
|
* @return {boolean}
|
||||||
|
* True if the node all whitespace, otherwise false.
|
||||||
|
*/
|
||||||
|
function isAllWhitespace(node) {
|
||||||
|
return !(/[^\t\n\r ]/.test(node.textContent));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether a node is a comment or whitespace node.
|
||||||
|
*
|
||||||
|
* @return {boolean}
|
||||||
|
* True if the node is a comment node or is all whitespace, otherwise false.
|
||||||
|
*/
|
||||||
|
function isIgnorable(node) {
|
||||||
|
return (node.nodeType == 8) || // A comment node
|
||||||
|
((node.nodeType == 3) && isAllWhitespace(node)); // text node, all ws
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next node, skipping comments and whitespace.
|
||||||
|
*
|
||||||
|
* @return {node}
|
||||||
|
* The next sibling node that is not a comment or whitespace, or null if
|
||||||
|
* there isn't one.
|
||||||
|
*/
|
||||||
|
function nodeAfter(sib) {
|
||||||
|
while ((sib = sib.nextSibling)) {
|
||||||
|
if (!isIgnorable(sib)) return sib;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether the argument `node` is a node whose tag is `tagName`.
|
||||||
|
*
|
||||||
|
* @param {node} node
|
||||||
|
* The code to test. May be null.
|
||||||
|
*
|
||||||
|
* @param {string} tagName
|
||||||
|
* The tag name to test against.
|
||||||
|
*
|
||||||
|
* @return {boolean}
|
||||||
|
* True if the node is not null and has the tag name `tagName`,
|
||||||
|
* otherwise false.
|
||||||
|
*/
|
||||||
|
function hasTagName(node, tagName) {
|
||||||
|
return node && node.tagName &&
|
||||||
|
node.tagName.toLowerCase() == tagName.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an MDN page, get the "summary" portion.
|
||||||
|
*
|
||||||
|
* This is the textContent of the first non-whitespace
|
||||||
|
* element in the #Summary section of the document.
|
||||||
|
*
|
||||||
|
* It's expected to be a <P> element.
|
||||||
|
*
|
||||||
|
* @param {Document} mdnDocument
|
||||||
|
* The document in which to look for the "summary" section.
|
||||||
|
*
|
||||||
|
* @return {string}
|
||||||
|
* The summary section as a string, or null if it could not be found.
|
||||||
|
*/
|
||||||
|
function getSummary(mdnDocument) {
|
||||||
|
let summary = mdnDocument.getElementById("Summary");
|
||||||
|
if (!hasTagName(summary, "H2")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let firstParagraph = nodeAfter(summary);
|
||||||
|
if (!hasTagName(firstParagraph, "P")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstParagraph.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an MDN page, get the "syntax" portion.
|
||||||
|
*
|
||||||
|
* First we get the #Syntax section of the document. The syntax
|
||||||
|
* section we want is somewhere inside there.
|
||||||
|
*
|
||||||
|
* If the page is in the old structure, then the *first two*
|
||||||
|
* non-whitespace elements in the #Syntax section will be <PRE>
|
||||||
|
* nodes, and the second of these will be the syntax section.
|
||||||
|
*
|
||||||
|
* If the page is in the new structure, then the only the *first*
|
||||||
|
* non-whitespace element in the #Syntax section will be a <PRE>
|
||||||
|
* node, and it will be the syntax section.
|
||||||
|
*
|
||||||
|
* @param {Document} mdnDocument
|
||||||
|
* The document in which to look for the "syntax" section.
|
||||||
|
*
|
||||||
|
* @return {string}
|
||||||
|
* The syntax section as a string, or null if it could not be found.
|
||||||
|
*/
|
||||||
|
function getSyntax(mdnDocument) {
|
||||||
|
|
||||||
|
let syntax = mdnDocument.getElementById("Syntax");
|
||||||
|
if (!hasTagName(syntax, "H2")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let firstParagraph = nodeAfter(syntax);
|
||||||
|
if (!hasTagName(firstParagraph, "PRE")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let secondParagraph = nodeAfter(firstParagraph);
|
||||||
|
if (hasTagName(secondParagraph, "PRE")) {
|
||||||
|
return secondParagraph.textContent;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return firstParagraph.textContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a different URL for CSS docs pages. Used only for testing.
|
||||||
|
*
|
||||||
|
* @param {string} baseUrl
|
||||||
|
* The baseURL to use.
|
||||||
|
*/
|
||||||
|
function setBaseCssDocsUrl(baseUrl) {
|
||||||
|
PAGE_LINK_URL = baseUrl;
|
||||||
|
XHR_CSS_URL = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.setBaseCssDocsUrl = setBaseCssDocsUrl;
|
@ -10,6 +10,7 @@ const IOService = Cc["@mozilla.org/network/io-service;1"]
|
|||||||
.getService(Ci.nsIIOService);
|
.getService(Ci.nsIIOService);
|
||||||
const {Spectrum} = require("devtools/shared/widgets/Spectrum");
|
const {Spectrum} = require("devtools/shared/widgets/Spectrum");
|
||||||
const {CubicBezierWidget} = require("devtools/shared/widgets/CubicBezierWidget");
|
const {CubicBezierWidget} = require("devtools/shared/widgets/CubicBezierWidget");
|
||||||
|
const {MdnDocsWidget} = require("devtools/shared/widgets/MdnDocsWidget");
|
||||||
const {CSSFilterEditorWidget} = require("devtools/shared/widgets/FilterWidget");
|
const {CSSFilterEditorWidget} = require("devtools/shared/widgets/FilterWidget");
|
||||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||||
const {colorUtils} = require("devtools/css-color");
|
const {colorUtils} = require("devtools/css-color");
|
||||||
@ -40,6 +41,7 @@ const BORDER_RE = /^border(-(top|bottom|left|right))?$/ig;
|
|||||||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||||
const SPECTRUM_FRAME = "chrome://browser/content/devtools/spectrum-frame.xhtml";
|
const SPECTRUM_FRAME = "chrome://browser/content/devtools/spectrum-frame.xhtml";
|
||||||
const CUBIC_BEZIER_FRAME = "chrome://browser/content/devtools/cubic-bezier-frame.xhtml";
|
const CUBIC_BEZIER_FRAME = "chrome://browser/content/devtools/cubic-bezier-frame.xhtml";
|
||||||
|
const MDN_DOCS_FRAME = "chrome://browser/content/devtools/mdn-docs-frame.xhtml";
|
||||||
const FILTER_FRAME = "chrome://browser/content/devtools/filter-frame.xhtml";
|
const FILTER_FRAME = "chrome://browser/content/devtools/filter-frame.xhtml";
|
||||||
const ESCAPE_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE;
|
const ESCAPE_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE;
|
||||||
const RETURN_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
|
const RETURN_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
|
||||||
@ -366,6 +368,8 @@ Tooltip.prototype = {
|
|||||||
* A function that accepts a node argument and returns true or false
|
* A function that accepts a node argument and returns true or false
|
||||||
* (or a promise that resolves or rejects) to signify if the tooltip
|
* (or a promise that resolves or rejects) to signify if the tooltip
|
||||||
* should be shown on that node or not.
|
* should be shown on that node or not.
|
||||||
|
* If the promise rejects, it must reject `false` as value.
|
||||||
|
* Any other value is going to be logged as unexpected error.
|
||||||
* Additionally, the function receives a second argument which is the
|
* Additionally, the function receives a second argument which is the
|
||||||
* tooltip instance itself, to be used to add/modify the content of the
|
* tooltip instance itself, to be used to add/modify the content of the
|
||||||
* tooltip if needed. If omitted, the tooltip will be shown everytime.
|
* tooltip if needed. If omitted, the tooltip will be shown everytime.
|
||||||
@ -733,30 +737,71 @@ Tooltip.prototype = {
|
|||||||
|
|
||||||
_getImageDimensionLabel: (w, h) => w + " \u00D7 " + h,
|
_getImageDimensionLabel: (w, h) => w + " \u00D7 " + h,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a document into an iframe, and set the iframe
|
||||||
|
* to be the tooltip's content.
|
||||||
|
*
|
||||||
|
* Used by tooltips that want to load their interface
|
||||||
|
* into an iframe from a URL.
|
||||||
|
*
|
||||||
|
* @param {string} width
|
||||||
|
* Width of the iframe.
|
||||||
|
* @param {string} height
|
||||||
|
* Height of the iframe.
|
||||||
|
* @param {string} url
|
||||||
|
* URL of the document to load into the iframe.
|
||||||
|
*
|
||||||
|
* @return {promise} A promise which is resolved with
|
||||||
|
* the iframe.
|
||||||
|
*
|
||||||
|
* This function creates an iframe, loads the specified document
|
||||||
|
* into it, sets the tooltip's content to the iframe, and returns
|
||||||
|
* a promise.
|
||||||
|
*
|
||||||
|
* When the document is loaded, the function gets the content window
|
||||||
|
* and resolves the promise with the content window.
|
||||||
|
*/
|
||||||
|
setIFrameContent: function({width, height}, url) {
|
||||||
|
|
||||||
|
let def = promise.defer();
|
||||||
|
|
||||||
|
// Create an iframe
|
||||||
|
let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
|
||||||
|
iframe.setAttribute("transparent", true);
|
||||||
|
iframe.setAttribute("width", width);
|
||||||
|
iframe.setAttribute("height", height);
|
||||||
|
iframe.setAttribute("flex", "1");
|
||||||
|
iframe.setAttribute("class", "devtools-tooltip-iframe");
|
||||||
|
|
||||||
|
// Wait for the load to initialize the widget
|
||||||
|
function onLoad() {
|
||||||
|
iframe.removeEventListener("load", onLoad, true);
|
||||||
|
def.resolve(iframe);
|
||||||
|
}
|
||||||
|
iframe.addEventListener("load", onLoad, true);
|
||||||
|
|
||||||
|
// load the document from url into the iframe
|
||||||
|
iframe.setAttribute("src", url);
|
||||||
|
|
||||||
|
// Put the iframe in the tooltip
|
||||||
|
this.content = iframe;
|
||||||
|
|
||||||
|
return def.promise;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill the tooltip with a new instance of the spectrum color picker widget
|
* Fill the tooltip with a new instance of the spectrum color picker widget
|
||||||
* initialized with the given color, and return a promise that resolves to
|
* initialized with the given color, and return a promise that resolves to
|
||||||
* the instance of spectrum
|
* the instance of spectrum
|
||||||
*/
|
*/
|
||||||
setColorPickerContent: function(color) {
|
setColorPickerContent: function(color) {
|
||||||
let def = promise.defer();
|
let dimensions = {width: "210", height: "216"};
|
||||||
|
|
||||||
// Create an iframe to contain spectrum
|
|
||||||
let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
|
|
||||||
iframe.setAttribute("transparent", true);
|
|
||||||
iframe.setAttribute("width", "210");
|
|
||||||
iframe.setAttribute("height", "216");
|
|
||||||
iframe.setAttribute("flex", "1");
|
|
||||||
iframe.setAttribute("class", "devtools-tooltip-iframe");
|
|
||||||
|
|
||||||
let panel = this.panel;
|
let panel = this.panel;
|
||||||
let xulWin = this.doc.ownerGlobal;
|
return this.setIFrameContent(dimensions, SPECTRUM_FRAME).then(onLoaded);
|
||||||
|
|
||||||
// Wait for the load to initialize spectrum
|
function onLoaded(iframe) {
|
||||||
function onLoad() {
|
|
||||||
iframe.removeEventListener("load", onLoad, true);
|
|
||||||
let win = iframe.contentWindow.wrappedJSObject;
|
let win = iframe.contentWindow.wrappedJSObject;
|
||||||
|
let def = promise.defer();
|
||||||
let container = win.document.getElementById("spectrum");
|
let container = win.document.getElementById("spectrum");
|
||||||
let spectrum = new Spectrum(container, color);
|
let spectrum = new Spectrum(container, color);
|
||||||
|
|
||||||
@ -775,14 +820,8 @@ Tooltip.prototype = {
|
|||||||
finalizeSpectrum();
|
finalizeSpectrum();
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
return def.promise;
|
||||||
}
|
}
|
||||||
iframe.addEventListener("load", onLoad, true);
|
|
||||||
iframe.setAttribute("src", SPECTRUM_FRAME);
|
|
||||||
|
|
||||||
// Put the iframe in the tooltip
|
|
||||||
this.content = iframe;
|
|
||||||
|
|
||||||
return def.promise;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -791,24 +830,13 @@ Tooltip.prototype = {
|
|||||||
* the instance of the widget
|
* the instance of the widget
|
||||||
*/
|
*/
|
||||||
setCubicBezierContent: function(bezier) {
|
setCubicBezierContent: function(bezier) {
|
||||||
let def = promise.defer();
|
let dimensions = {width: "410", height: "360"};
|
||||||
|
|
||||||
// Create an iframe to host the cubic-bezier widget
|
|
||||||
let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
|
|
||||||
iframe.setAttribute("transparent", true);
|
|
||||||
iframe.setAttribute("width", "410");
|
|
||||||
iframe.setAttribute("height", "360");
|
|
||||||
iframe.setAttribute("flex", "1");
|
|
||||||
iframe.setAttribute("class", "devtools-tooltip-iframe");
|
|
||||||
|
|
||||||
let panel = this.panel;
|
let panel = this.panel;
|
||||||
let xulWin = this.doc.ownerGlobal;
|
return this.setIFrameContent(dimensions, CUBIC_BEZIER_FRAME).then(onLoaded);
|
||||||
|
|
||||||
// Wait for the load to initialize the widget
|
function onLoaded(iframe) {
|
||||||
function onLoad() {
|
|
||||||
iframe.removeEventListener("load", onLoad, true);
|
|
||||||
let win = iframe.contentWindow.wrappedJSObject;
|
let win = iframe.contentWindow.wrappedJSObject;
|
||||||
|
let def = promise.defer();
|
||||||
let container = win.document.getElementById("container");
|
let container = win.document.getElementById("container");
|
||||||
let widget = new CubicBezierWidget(container, bezier);
|
let widget = new CubicBezierWidget(container, bezier);
|
||||||
|
|
||||||
@ -821,14 +849,8 @@ Tooltip.prototype = {
|
|||||||
def.resolve(widget);
|
def.resolve(widget);
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
return def.promise;
|
||||||
}
|
}
|
||||||
iframe.addEventListener("load", onLoad, true);
|
|
||||||
iframe.setAttribute("src", CUBIC_BEZIER_FRAME);
|
|
||||||
|
|
||||||
// Put the iframe in the tooltip
|
|
||||||
this.content = iframe;
|
|
||||||
|
|
||||||
return def.promise;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -837,29 +859,21 @@ Tooltip.prototype = {
|
|||||||
* that resolves to the instance of the widget when ready.
|
* that resolves to the instance of the widget when ready.
|
||||||
*/
|
*/
|
||||||
setFilterContent: function(filter) {
|
setFilterContent: function(filter) {
|
||||||
let def = promise.defer();
|
let dimensions = {width: "350", height: "350"};
|
||||||
|
|
||||||
// Create an iframe to host the filter widget
|
|
||||||
let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
|
|
||||||
iframe.setAttribute("transparent", true);
|
|
||||||
iframe.setAttribute("width", "350");
|
|
||||||
iframe.setAttribute("flex", "1");
|
|
||||||
iframe.setAttribute("class", "devtools-tooltip-iframe");
|
|
||||||
|
|
||||||
let panel = this.panel;
|
let panel = this.panel;
|
||||||
|
return this.setIFrameContent(dimensions, FILTER_FRAME).then(onLoaded);
|
||||||
|
|
||||||
function onLoad() {
|
function onLoaded(iframe) {
|
||||||
iframe.removeEventListener("load", onLoad, true);
|
let win = iframe.contentWindow.wrappedJSObject;
|
||||||
let win = iframe.contentWindow.wrappedJSObject,
|
let doc = win.document.documentElement;
|
||||||
doc = win.document.documentElement;
|
let def = promise.defer();
|
||||||
|
|
||||||
let container = win.document.getElementById("container");
|
let container = win.document.getElementById("container");
|
||||||
let widget = new CSSFilterEditorWidget(container, filter);
|
let widget = new CSSFilterEditorWidget(container, filter);
|
||||||
|
|
||||||
iframe.height = doc.offsetHeight
|
iframe.height = doc.offsetHeight;
|
||||||
|
|
||||||
widget.on("render", e => {
|
widget.on("render", e => {
|
||||||
iframe.height = doc.offsetHeight
|
iframe.height = doc.offsetHeight;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Resolve to the widget instance whenever the popup becomes visible
|
// Resolve to the widget instance whenever the popup becomes visible
|
||||||
@ -871,14 +885,8 @@ Tooltip.prototype = {
|
|||||||
def.resolve(widget);
|
def.resolve(widget);
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
return def.promise;
|
||||||
}
|
}
|
||||||
iframe.addEventListener("load", onLoad, true);
|
|
||||||
iframe.setAttribute("src", FILTER_FRAME);
|
|
||||||
|
|
||||||
// Put the iframe in the tooltip
|
|
||||||
this.content = iframe;
|
|
||||||
|
|
||||||
return def.promise;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -909,7 +917,33 @@ Tooltip.prototype = {
|
|||||||
let str = yield data.string();
|
let str = yield data.string();
|
||||||
this.setImageContent(str, { hideDimensionLabel: true, maxDim: size });
|
this.setImageContent(str, { hideDimensionLabel: true, maxDim: size });
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the content of this tooltip to the MDN docs widget.
|
||||||
|
*
|
||||||
|
* This is called when the tooltip is first constructed.
|
||||||
|
*
|
||||||
|
* @return {promise} A promise which is resolved with an MdnDocsWidget.
|
||||||
|
*
|
||||||
|
* It loads the tooltip's structure from a separate XHTML file
|
||||||
|
* into an iframe. When the iframe is loaded it constructs
|
||||||
|
* an MdnDocsWidget and passes that into resolve.
|
||||||
|
*
|
||||||
|
* The caller can use the MdnDocsWidget to update the tooltip's
|
||||||
|
* UI with new content each time the tooltip is shown.
|
||||||
|
*/
|
||||||
|
setMdnDocsContent: function() {
|
||||||
|
let dimensions = {width: "410", height: "300"};
|
||||||
|
return this.setIFrameContent(dimensions, MDN_DOCS_FRAME).then(onLoaded);
|
||||||
|
|
||||||
|
function onLoaded(iframe) {
|
||||||
|
let win = iframe.contentWindow.wrappedJSObject;
|
||||||
|
// create an MdnDocsWidget, initializing it with the content document
|
||||||
|
let widget = new MdnDocsWidget(win.document);
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1535,6 +1569,46 @@ SwatchCubicBezierTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.pr
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tooltip for displaying docs for CSS properties from MDN.
|
||||||
|
*
|
||||||
|
* @param {XULDocument} doc
|
||||||
|
*/
|
||||||
|
function CssDocsTooltip(doc) {
|
||||||
|
this.tooltip = new Tooltip(doc, {
|
||||||
|
consumeOutsideClick: true,
|
||||||
|
closeOnKeys: [ESCAPE_KEYCODE, RETURN_KEYCODE],
|
||||||
|
noAutoFocus: false
|
||||||
|
});
|
||||||
|
this.widget = this.tooltip.setMdnDocsContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.CssDocsTooltip = CssDocsTooltip;
|
||||||
|
|
||||||
|
CssDocsTooltip.prototype = {
|
||||||
|
/**
|
||||||
|
* Load CSS docs for the given property,
|
||||||
|
* then display the tooltip.
|
||||||
|
*/
|
||||||
|
show: function(anchor, propertyName) {
|
||||||
|
|
||||||
|
function loadCssDocs(widget) {
|
||||||
|
return widget.loadCssDocs(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.widget.then(loadCssDocs);
|
||||||
|
this.tooltip.show(anchor, "topcenter bottomleft");
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function() {
|
||||||
|
this.tooltip.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
this.tooltip.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The swatch-based css filter tooltip class is a specific class meant to be used
|
* The swatch-based css filter tooltip class is a specific class meant to be used
|
||||||
* along with rule-view's generated css filter swatches.
|
* along with rule-view's generated css filter swatches.
|
||||||
|
34
browser/devtools/shared/widgets/mdn-docs-frame.xhtml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?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"/>
|
||||||
|
<link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
|
||||||
|
<link rel="stylesheet" href="chrome://browser/content/devtools/mdn-docs.css" type="text/css"/>
|
||||||
|
<script type="application/javascript;version=1.8" src="theme-switching.js"/>
|
||||||
|
</head>
|
||||||
|
<body class="theme-body">
|
||||||
|
|
||||||
|
<div id = "container">
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1 id="property-name" class="theme-fg-color5"></h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="property-info">
|
||||||
|
<div id="summary"></div>
|
||||||
|
<pre id="syntax" class="devtools-monospace"></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a id="visit-mdn-page" class="theme-link" href="#">Visit MDN (placeholder)</a>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
41
browser/devtools/shared/widgets/mdn-docs.css
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#visit-mdn-page {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, #container {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
header, footer {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#property-info {
|
||||||
|
flex: 10;
|
||||||
|
padding: 0 1em;
|
||||||
|
overflow: auto;
|
||||||
|
transition: opacity 400ms ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
#syntax {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.devtools-throbber {
|
||||||
|
opacity: 0;
|
||||||
|
align-self: center;
|
||||||
|
}
|
@ -1561,8 +1561,8 @@ SelectorView.prototype = {
|
|||||||
contentDoc = rawNode.ownerDocument;
|
contentDoc = rawNode.ownerDocument;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let viewSourceUtils = inspector.viewSourceUtils;
|
let toolbox = gDevTools.getToolbox(inspector.target);
|
||||||
viewSourceUtils.viewSource(rule.href, null, contentDoc, rule.line);
|
toolbox.viewSource(rule.href, rule.line);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ const HTML_NS = "http://www.w3.org/1999/xhtml";
|
|||||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||||
const PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles";
|
const PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles";
|
||||||
const PREF_DEFAULT_COLOR_UNIT = "devtools.defaultColorUnit";
|
const PREF_DEFAULT_COLOR_UNIT = "devtools.defaultColorUnit";
|
||||||
|
const PROPERTY_NAME_CLASS = "ruleview-propertyname";
|
||||||
const FILTER_CHANGED_TIMEOUT = 150;
|
const FILTER_CHANGED_TIMEOUT = 150;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1126,6 +1127,7 @@ function CssRuleView(aInspector, aDoc, aStore, aPageStyle) {
|
|||||||
this._onCopy = this._onCopy.bind(this);
|
this._onCopy = this._onCopy.bind(this);
|
||||||
this._onCopyColor = this._onCopyColor.bind(this);
|
this._onCopyColor = this._onCopyColor.bind(this);
|
||||||
this._onToggleOrigSources = this._onToggleOrigSources.bind(this);
|
this._onToggleOrigSources = this._onToggleOrigSources.bind(this);
|
||||||
|
this._onShowMdnDocs = this._onShowMdnDocs.bind(this);
|
||||||
this._onFilterStyles = this._onFilterStyles.bind(this);
|
this._onFilterStyles = this._onFilterStyles.bind(this);
|
||||||
this._onClearSearch = this._onClearSearch.bind(this);
|
this._onClearSearch = this._onClearSearch.bind(this);
|
||||||
this._onFilterTextboxContextMenu = this._onFilterTextboxContextMenu.bind(this);
|
this._onFilterTextboxContextMenu = this._onFilterTextboxContextMenu.bind(this);
|
||||||
@ -1216,6 +1218,12 @@ CssRuleView.prototype = {
|
|||||||
type: "checkbox"
|
type: "checkbox"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.menuitemShowMdnDocs = createMenuItem(this._contextmenu, {
|
||||||
|
label: "ruleView.contextmenu.showMdnDocs",
|
||||||
|
accesskey: "ruleView.contextmenu.showMdnDocs.accessKey",
|
||||||
|
command: this._onShowMdnDocs
|
||||||
|
});
|
||||||
|
|
||||||
let popupset = doc.documentElement.querySelector("popupset");
|
let popupset = doc.documentElement.querySelector("popupset");
|
||||||
if (!popupset) {
|
if (!popupset) {
|
||||||
popupset = doc.createElementNS(XUL_NS, "popupset");
|
popupset = doc.createElementNS(XUL_NS, "popupset");
|
||||||
@ -1342,6 +1350,9 @@ CssRuleView.prototype = {
|
|||||||
var showOrig = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
|
var showOrig = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
|
||||||
this.menuitemSources.setAttribute("checked", showOrig);
|
this.menuitemSources.setAttribute("checked", showOrig);
|
||||||
|
|
||||||
|
this.menuitemShowMdnDocs.hidden = !this.doc.popupNode.parentNode
|
||||||
|
.classList.contains(PROPERTY_NAME_CLASS);
|
||||||
|
|
||||||
this.menuitemAddRule.disabled = this.inspector.selection.isAnonymousNode();
|
this.menuitemAddRule.disabled = this.inspector.selection.isAnonymousNode();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1363,7 +1374,7 @@ CssRuleView.prototype = {
|
|||||||
let classes = node.classList;
|
let classes = node.classList;
|
||||||
let prop = getParentTextProperty(node);
|
let prop = getParentTextProperty(node);
|
||||||
|
|
||||||
if (classes.contains("ruleview-propertyname") && prop) {
|
if (classes.contains(PROPERTY_NAME_CLASS) && prop) {
|
||||||
type = overlays.VIEW_NODE_PROPERTY_TYPE;
|
type = overlays.VIEW_NODE_PROPERTY_TYPE;
|
||||||
value = {
|
value = {
|
||||||
property: node.textContent,
|
property: node.textContent,
|
||||||
@ -1518,6 +1529,16 @@ CssRuleView.prototype = {
|
|||||||
Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled);
|
Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show docs from MDN for a CSS property.
|
||||||
|
*/
|
||||||
|
_onShowMdnDocs: function() {
|
||||||
|
let cssPropertyName = this.doc.popupNode.textContent;
|
||||||
|
let anchor = this.doc.popupNode.parentNode;
|
||||||
|
let cssDocsTooltip = this.tooltips.cssDocs;
|
||||||
|
cssDocsTooltip.show(anchor, cssPropertyName);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new rule to the current element.
|
* Add a new rule to the current element.
|
||||||
*/
|
*/
|
||||||
@ -2410,7 +2431,7 @@ RuleEditor.prototype = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.newPropSpan = createChild(this.newPropItem, "span", {
|
this.newPropSpan = createChild(this.newPropItem, "span", {
|
||||||
class: "ruleview-propertyname",
|
class: PROPERTY_NAME_CLASS,
|
||||||
tabindex: "0"
|
tabindex: "0"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ const {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
SwatchColorPickerTooltip,
|
SwatchColorPickerTooltip,
|
||||||
SwatchCubicBezierTooltip,
|
SwatchCubicBezierTooltip,
|
||||||
|
CssDocsTooltip,
|
||||||
SwatchFilterTooltip
|
SwatchFilterTooltip
|
||||||
} = require("devtools/shared/widgets/Tooltip");
|
} = require("devtools/shared/widgets/Tooltip");
|
||||||
const {CssLogic} = require("devtools/styleinspector/css-logic");
|
const {CssLogic} = require("devtools/styleinspector/css-logic");
|
||||||
@ -263,6 +264,8 @@ TooltipsOverlay.prototype = {
|
|||||||
this.colorPicker = new SwatchColorPickerTooltip(this.view.inspector.panelDoc);
|
this.colorPicker = new SwatchColorPickerTooltip(this.view.inspector.panelDoc);
|
||||||
// Cubic bezier tooltip
|
// Cubic bezier tooltip
|
||||||
this.cubicBezier = new SwatchCubicBezierTooltip(this.view.inspector.panelDoc);
|
this.cubicBezier = new SwatchCubicBezierTooltip(this.view.inspector.panelDoc);
|
||||||
|
// MDN CSS help tooltip
|
||||||
|
this.cssDocs = new CssDocsTooltip(this.view.inspector.panelDoc);
|
||||||
// Filter editor tooltip
|
// Filter editor tooltip
|
||||||
this.filterEditor = new SwatchFilterTooltip(this.view.inspector.panelDoc);
|
this.filterEditor = new SwatchFilterTooltip(this.view.inspector.panelDoc);
|
||||||
}
|
}
|
||||||
@ -290,6 +293,10 @@ TooltipsOverlay.prototype = {
|
|||||||
this.cubicBezier.destroy();
|
this.cubicBezier.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.cssDocs) {
|
||||||
|
this.cssDocs.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.filterEditor) {
|
if (this.filterEditor) {
|
||||||
this.filterEditor.destroy();
|
this.filterEditor.destroy();
|
||||||
}
|
}
|
||||||
@ -334,13 +341,13 @@ TooltipsOverlay.prototype = {
|
|||||||
let nodeInfo = this.view.getNodeInfo(target);
|
let nodeInfo = this.view.getNodeInfo(target);
|
||||||
if (!nodeInfo) {
|
if (!nodeInfo) {
|
||||||
// The hovered node isn't something we care about
|
// The hovered node isn't something we care about
|
||||||
return promise.reject();
|
return promise.reject(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let type = this._getTooltipType(nodeInfo);
|
let type = this._getTooltipType(nodeInfo);
|
||||||
if (!type) {
|
if (!type) {
|
||||||
// There is no tooltip type defined for the hovered node
|
// There is no tooltip type defined for the hovered node
|
||||||
return promise.reject();
|
return promise.reject(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isRuleView && this.colorPicker.tooltip.isShown()) {
|
if (this.isRuleView && this.colorPicker.tooltip.isShown()) {
|
||||||
@ -353,6 +360,10 @@ TooltipsOverlay.prototype = {
|
|||||||
this.cubicBezier.hide();
|
this.cubicBezier.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isRuleView && this.cssDocs.tooltip.isShown()) {
|
||||||
|
this.cssDocs.hide();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isRuleView && this.filterEditor.tooltip.isShown()) {
|
if (this.isRuleView && this.filterEditor.tooltip.isShown()) {
|
||||||
this.filterEditor.revert();
|
this.filterEditor.revert();
|
||||||
this.filterEdtior.hide();
|
this.filterEdtior.hide();
|
||||||
@ -387,6 +398,10 @@ TooltipsOverlay.prototype = {
|
|||||||
this.cubicBezier.hide();
|
this.cubicBezier.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.cssDocs) {
|
||||||
|
this.cssDocs.hide();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.filterEditor) {
|
if (this.filterEditor) {
|
||||||
this.filterEditor.hide();
|
this.filterEditor.hide();
|
||||||
}
|
}
|
||||||
|
@ -110,9 +110,9 @@ RuleViewTool.prototype = {
|
|||||||
// these sheets in the view source window instead.
|
// these sheets in the view source window instead.
|
||||||
if (!sheet || sheet.isSystem) {
|
if (!sheet || sheet.isSystem) {
|
||||||
let contentDoc = this.inspector.selection.document;
|
let contentDoc = this.inspector.selection.document;
|
||||||
let viewSourceUtils = this.inspector.viewSourceUtils;
|
|
||||||
let href = rule.nodeHref || rule.href;
|
let href = rule.nodeHref || rule.href;
|
||||||
viewSourceUtils.viewSource(href, null, contentDoc, rule.line || 0);
|
let toolbox = gDevTools.getToolbox(this.inspector.target);
|
||||||
|
toolbox.viewSource(href, rule.line);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +70,8 @@ support-files =
|
|||||||
[browser_ruleview_content_01.js]
|
[browser_ruleview_content_01.js]
|
||||||
[browser_ruleview_content_02.js]
|
[browser_ruleview_content_02.js]
|
||||||
skip-if = e10s # Bug 1039528: "inspect element" contextual-menu doesn't work with e10s
|
skip-if = e10s # Bug 1039528: "inspect element" contextual-menu doesn't work with e10s
|
||||||
|
[browser_ruleview_context-menu-show-mdn-docs-01.js]
|
||||||
|
[browser_ruleview_context-menu-show-mdn-docs-02.js]
|
||||||
[browser_ruleview_cubicbezier-appears-on-swatch-click.js]
|
[browser_ruleview_cubicbezier-appears-on-swatch-click.js]
|
||||||
[browser_ruleview_cubicbezier-commit-on-ENTER.js]
|
[browser_ruleview_cubicbezier-commit-on-ENTER.js]
|
||||||
[browser_ruleview_cubicbezier-revert-on-ESC.js]
|
[browser_ruleview_cubicbezier-revert-on-ESC.js]
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file tests the code that integrates the Style Inspector's rule view
|
||||||
|
* with the MDN docs tooltip.
|
||||||
|
*
|
||||||
|
* If you display the context click on a property name in the rule view, you
|
||||||
|
* should see a menu item "Show MDN Docs". If you click that item, the MDN
|
||||||
|
* docs tooltip should be shown, containing docs from MDN for that property.
|
||||||
|
*
|
||||||
|
* This file tests that the context menu item is shown when it should be
|
||||||
|
* shown and hidden when it should be hidden.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {setBaseCssDocsUrl} = devtools.require("devtools/shared/widgets/MdnDocsWidget");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The test document tries to confuse the context menu
|
||||||
|
* code by having a tag called "padding" and a property
|
||||||
|
* value called "margin".
|
||||||
|
*/
|
||||||
|
const TEST_DOC =`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
padding {font-family: margin;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<padding>MDN tooltip testing</padding>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
add_task(function* () {
|
||||||
|
|
||||||
|
yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_DOC));
|
||||||
|
|
||||||
|
let {inspector, view} = yield openRuleView();
|
||||||
|
yield selectNode("padding", inspector);
|
||||||
|
|
||||||
|
yield testMdnContextMenuItemVisibility(view);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the MDN context menu item is shown when it should be,
|
||||||
|
* and hidden when it should be.
|
||||||
|
* - iterate through every node in the rule view
|
||||||
|
* - set that node as popupNode (the node that the context menu
|
||||||
|
* is shown for)
|
||||||
|
* - update the context menu's state
|
||||||
|
* - test that the MDN context menu item is hidden, or not,
|
||||||
|
* depending on popupNode
|
||||||
|
*/
|
||||||
|
function* testMdnContextMenuItemVisibility(view) {
|
||||||
|
info("Test that MDN context menu item is shown only when it should be.");
|
||||||
|
|
||||||
|
let root = rootElement(view);
|
||||||
|
for (let node of iterateNodes(root)) {
|
||||||
|
info("Setting " + node + " as popupNode");
|
||||||
|
view.doc.popupNode = node;
|
||||||
|
|
||||||
|
info("Updating context menu state");
|
||||||
|
view._contextMenuUpdate();
|
||||||
|
let isVisible = !view.menuitemShowMdnDocs.hidden;
|
||||||
|
let shouldBeVisible = isPropertyNameNode(node);
|
||||||
|
let message = shouldBeVisible? "shown": "hidden";
|
||||||
|
is(isVisible, shouldBeVisible,
|
||||||
|
"The MDN context menu item is " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a node is a property name.
|
||||||
|
*/
|
||||||
|
function isPropertyNameNode(node) {
|
||||||
|
return ((node.nodeType === node.TEXT_NODE) &&
|
||||||
|
(node.textContent === "font-family"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generator that iterates recursively through all child nodes of baseNode.
|
||||||
|
*/
|
||||||
|
function* iterateNodes(baseNode) {
|
||||||
|
yield baseNode;
|
||||||
|
|
||||||
|
for (let child of baseNode.childNodes) {
|
||||||
|
yield* iterateNodes(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the root element for the rule view.
|
||||||
|
*/
|
||||||
|
let rootElement = view => (view.element) ? view.element : view.styleDocument;
|
@ -0,0 +1,89 @@
|
|||||||
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file tests the code that integrates the Style Inspector's rule view
|
||||||
|
* with the MDN docs tooltip.
|
||||||
|
*
|
||||||
|
* If you display the context click on a property name in the rule view, you
|
||||||
|
* should see a menu item "Show MDN Docs". If you click that item, the MDN
|
||||||
|
* docs tooltip should be shown, containing docs from MDN for that property.
|
||||||
|
*
|
||||||
|
* This file tests that:
|
||||||
|
* - clicking the context menu item shows the tooltip
|
||||||
|
* - pressing "Escape" while the tooltip is showing hides the tooltip
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {setBaseCssDocsUrl} = devtools.require("devtools/shared/widgets/MdnDocsWidget");
|
||||||
|
|
||||||
|
const PROPERTYNAME = "color";
|
||||||
|
|
||||||
|
const TEST_DOC = `
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div style="color: red">
|
||||||
|
Test "Show MDN Docs" context menu option
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
add_task(function* () {
|
||||||
|
|
||||||
|
yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_DOC));
|
||||||
|
|
||||||
|
let {inspector, view} = yield openRuleView();
|
||||||
|
yield selectNode("div", inspector);
|
||||||
|
|
||||||
|
yield testShowAndHideMdnTooltip(view);
|
||||||
|
});
|
||||||
|
|
||||||
|
function* testShowMdnTooltip(view) {
|
||||||
|
setBaseCssDocsUrl(TEST_URL_ROOT);
|
||||||
|
|
||||||
|
info("Setting the popupNode for the MDN docs tooltip");
|
||||||
|
|
||||||
|
let {nameSpan} = getRuleViewProperty(view, "element", PROPERTYNAME);
|
||||||
|
|
||||||
|
view.doc.popupNode = nameSpan.firstChild;
|
||||||
|
view._contextMenuUpdate();
|
||||||
|
|
||||||
|
let cssDocs = view.tooltips.cssDocs;
|
||||||
|
|
||||||
|
info("Showing the MDN docs tooltip");
|
||||||
|
let onShown = cssDocs.tooltip.once("shown");
|
||||||
|
view.menuitemShowMdnDocs.click();
|
||||||
|
yield onShown;
|
||||||
|
ok(true, "The MDN docs tooltip was shown");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that:
|
||||||
|
* - the MDN tooltip is shown when we click the context menu item
|
||||||
|
* - the tooltip's contents have been initialized (we don't fully
|
||||||
|
* test this here, as it's fully tested with the tooltip test code)
|
||||||
|
* - the tooltip is hidden when we press Escape
|
||||||
|
*/
|
||||||
|
function* testShowAndHideMdnTooltip(view) {
|
||||||
|
yield testShowMdnTooltip(view);
|
||||||
|
|
||||||
|
info("Quick check that the tooltip contents are set");
|
||||||
|
let cssDocs = view.tooltips.cssDocs;
|
||||||
|
|
||||||
|
let tooltipDocument = cssDocs.tooltip.content.contentDocument;
|
||||||
|
let h1 = tooltipDocument.getElementById("property-name");
|
||||||
|
is(h1.textContent, PROPERTYNAME, "The MDN docs tooltip h1 is correct");
|
||||||
|
|
||||||
|
info("Simulate pressing the 'Escape' key");
|
||||||
|
let onHidden = cssDocs.tooltip.once("hidden");
|
||||||
|
EventUtils.sendKey("escape");
|
||||||
|
yield onHidden;
|
||||||
|
ok(true, "The MDN docs tooltip was hidden on pressing 'escape'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the root element for the rule view.
|
||||||
|
*/
|
||||||
|
let rootElement = view => (view.element) ? view.element : view.styleDocument;
|
@ -20,6 +20,7 @@ loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
|
|||||||
loader.lazyImporter(this, "DebuggerServer", "resource://gre/modules/devtools/dbg-server.jsm");
|
loader.lazyImporter(this, "DebuggerServer", "resource://gre/modules/devtools/dbg-server.jsm");
|
||||||
loader.lazyImporter(this, "DebuggerClient", "resource://gre/modules/devtools/dbg-client.jsm");
|
loader.lazyImporter(this, "DebuggerClient", "resource://gre/modules/devtools/dbg-client.jsm");
|
||||||
loader.lazyGetter(this, "showDoorhanger", () => require("devtools/shared/doorhanger").showDoorhanger);
|
loader.lazyGetter(this, "showDoorhanger", () => require("devtools/shared/doorhanger").showDoorhanger);
|
||||||
|
loader.lazyRequireGetter(this, "sourceUtils", "devtools/shared/source-utils");
|
||||||
|
|
||||||
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
|
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
|
||||||
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
|
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
|
||||||
@ -432,10 +433,8 @@ WebConsole.prototype = {
|
|||||||
* @param integer aSourceLine
|
* @param integer aSourceLine
|
||||||
* The line number which should be highlighted.
|
* The line number which should be highlighted.
|
||||||
*/
|
*/
|
||||||
viewSource: function WC_viewSource(aSourceURL, aSourceLine)
|
viewSource: function WC_viewSource(aSourceURL, aSourceLine) {
|
||||||
{
|
this.gViewSourceUtils.viewSource(aSourceURL, null, this.iframeWindow.document, aSourceLine || 0);
|
||||||
this.gViewSourceUtils.viewSource(aSourceURL, null,
|
|
||||||
this.iframeWindow.document, aSourceLine);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -443,30 +442,20 @@ WebConsole.prototype = {
|
|||||||
* instance in the Style Editor. If the file is not found, it is opened in
|
* instance in the Style Editor. If the file is not found, it is opened in
|
||||||
* source view instead.
|
* source view instead.
|
||||||
*
|
*
|
||||||
|
* Manually handle the case where toolbox does not exist (Browser Console).
|
||||||
|
*
|
||||||
* @param string aSourceURL
|
* @param string aSourceURL
|
||||||
* The URL of the file.
|
* The URL of the file.
|
||||||
* @param integer aSourceLine
|
* @param integer aSourceLine
|
||||||
* The line number which you want to place the caret.
|
* The line number which you want to place the caret.
|
||||||
* TODO: This function breaks the client-server boundaries.
|
|
||||||
* To be fixed in bug 793259.
|
|
||||||
*/
|
*/
|
||||||
viewSourceInStyleEditor:
|
viewSourceInStyleEditor: function WC_viewSourceInStyleEditor(aSourceURL, aSourceLine) {
|
||||||
function WC_viewSourceInStyleEditor(aSourceURL, aSourceLine)
|
|
||||||
{
|
|
||||||
let toolbox = gDevTools.getToolbox(this.target);
|
let toolbox = gDevTools.getToolbox(this.target);
|
||||||
if (!toolbox) {
|
if (!toolbox) {
|
||||||
this.viewSource(aSourceURL, aSourceLine);
|
this.viewSource(aSourceURL, aSourceLine);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
toolbox.viewSourceInStyleEditor(aSourceURL, aSourceLine);
|
||||||
gDevTools.showToolbox(this.target, "styleeditor").then(function(toolbox) {
|
|
||||||
try {
|
|
||||||
toolbox.getCurrentPanel().selectStyleSheet(aSourceURL, aSourceLine);
|
|
||||||
} catch(e) {
|
|
||||||
// Open view source if style editor fails.
|
|
||||||
this.viewSource(aSourceURL, aSourceLine);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -474,49 +463,24 @@ WebConsole.prototype = {
|
|||||||
* instance in the Script Debugger. If the file is not found, it is opened in
|
* instance in the Script Debugger. If the file is not found, it is opened in
|
||||||
* source view instead.
|
* source view instead.
|
||||||
*
|
*
|
||||||
|
* Manually handle the case where toolbox does not exist (Browser Console).
|
||||||
|
*
|
||||||
* @param string aSourceURL
|
* @param string aSourceURL
|
||||||
* The URL of the file.
|
* The URL of the file.
|
||||||
* @param integer aSourceLine
|
* @param integer aSourceLine
|
||||||
* The line number which you want to place the caret.
|
* The line number which you want to place the caret.
|
||||||
*/
|
*/
|
||||||
viewSourceInDebugger:
|
viewSourceInDebugger: function WC_viewSourceInDebugger(aSourceURL, aSourceLine) {
|
||||||
function WC_viewSourceInDebugger(aSourceURL, aSourceLine)
|
|
||||||
{
|
|
||||||
let toolbox = gDevTools.getToolbox(this.target);
|
let toolbox = gDevTools.getToolbox(this.target);
|
||||||
if (!toolbox) {
|
if (!toolbox) {
|
||||||
this.viewSource(aSourceURL, aSourceLine);
|
this.viewSource(aSourceURL, aSourceLine);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
toolbox.viewSourceInDebugger(aSourceURL, aSourceLine).then(() => {
|
||||||
let showSource = ({ DebuggerView }) => {
|
this.ui.emit("source-in-debugger-opened");
|
||||||
let item = DebuggerView.Sources.getItemForAttachment(
|
})
|
||||||
a => a.source.url === aSourceURL
|
|
||||||
);
|
|
||||||
if (item) {
|
|
||||||
DebuggerView.setEditorLocation(item.attachment.source.actor, aSourceLine,
|
|
||||||
{ noDebug: true }).then(() => {
|
|
||||||
this.ui.emit("source-in-debugger-opened");
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
toolbox.selectTool("webconsole")
|
|
||||||
.then(() => this.viewSource(aSourceURL, aSourceLine));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the Debugger was already open, switch to it and try to show the
|
|
||||||
// source immediately. Otherwise, initialize it and wait for the sources
|
|
||||||
// to be added first.
|
|
||||||
let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger");
|
|
||||||
toolbox.selectTool("jsdebugger").then(({ panelWin: dbg }) => {
|
|
||||||
if (debuggerAlreadyOpen) {
|
|
||||||
showSource(dbg);
|
|
||||||
} else {
|
|
||||||
dbg.once(dbg.EVENTS.SOURCES_ADDED, () => showSource(dbg));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to open a JavaScript file related to the web page for the web console
|
* Tries to open a JavaScript file related to the web page for the web console
|
||||||
* instance in the corresponding Scratchpad.
|
* instance in the corresponding Scratchpad.
|
||||||
@ -524,33 +488,8 @@ WebConsole.prototype = {
|
|||||||
* @param string aSourceURL
|
* @param string aSourceURL
|
||||||
* The URL of the file which corresponds to a Scratchpad id.
|
* The URL of the file which corresponds to a Scratchpad id.
|
||||||
*/
|
*/
|
||||||
viewSourceInScratchpad: function WC_viewSourceInScratchpad(aSourceURL)
|
viewSourceInScratchpad: function WC_viewSourceInScratchpad(aSourceURL, aSourceLine) {
|
||||||
{
|
sourceUtils.viewSourceInScratchpad(aSourceURL, aSourceLine);
|
||||||
// Check for matching top level Scratchpad window.
|
|
||||||
let wins = Services.wm.getEnumerator("devtools:scratchpad");
|
|
||||||
|
|
||||||
while (wins.hasMoreElements()) {
|
|
||||||
let win = wins.getNext();
|
|
||||||
|
|
||||||
if (!win.closed && win.Scratchpad.uniqueName === aSourceURL) {
|
|
||||||
win.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for matching Scratchpad toolbox tab.
|
|
||||||
for (let [, toolbox] of gDevTools) {
|
|
||||||
let scratchpadPanel = toolbox.getPanel("scratchpad");
|
|
||||||
if (scratchpadPanel) {
|
|
||||||
let { scratchpad } = scratchpadPanel;
|
|
||||||
if (scratchpad.uniqueName === aSourceURL) {
|
|
||||||
toolbox.selectTool("scratchpad");
|
|
||||||
toolbox.raise();
|
|
||||||
scratchpad.editor.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,6 +76,7 @@ support-files =
|
|||||||
test-console-extras.html
|
test-console-extras.html
|
||||||
test-console-replaced-api.html
|
test-console-replaced-api.html
|
||||||
test-console.html
|
test-console.html
|
||||||
|
test-console-workers.html
|
||||||
test-console-table.html
|
test-console-table.html
|
||||||
test-console-output-02.html
|
test-console-output-02.html
|
||||||
test-console-output-03.html
|
test-console-output-03.html
|
||||||
@ -317,6 +318,7 @@ skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
|
|||||||
[browser_webconsole_completion.js]
|
[browser_webconsole_completion.js]
|
||||||
[browser_webconsole_console_extras.js]
|
[browser_webconsole_console_extras.js]
|
||||||
[browser_webconsole_console_logging_api.js]
|
[browser_webconsole_console_logging_api.js]
|
||||||
|
[browser_webconsole_console_logging_workers_api.js]
|
||||||
[browser_webconsole_count.js]
|
[browser_webconsole_count.js]
|
||||||
[browser_webconsole_dont_navigate_on_doubleclick.js]
|
[browser_webconsole_dont_navigate_on_doubleclick.js]
|
||||||
[browser_webconsole_execution_scope.js]
|
[browser_webconsole_execution_scope.js]
|
||||||
|
@ -67,8 +67,8 @@ function test()
|
|||||||
let viewSource = browserconsole.viewSource;
|
let viewSource = browserconsole.viewSource;
|
||||||
let URL = null;
|
let URL = null;
|
||||||
let clickPromise = promise.defer();
|
let clickPromise = promise.defer();
|
||||||
browserconsole.viewSource = (aURL) => {
|
browserconsole.viewSourceInDebugger = (aURL) => {
|
||||||
info("browserconsole.viewSource() was invoked: " + aURL);
|
info("browserconsole.viewSourceInDebugger() was invoked: " + aURL);
|
||||||
URL = aURL;
|
URL = aURL;
|
||||||
clickPromise.resolve(null);
|
clickPromise.resolve(null);
|
||||||
};
|
};
|
||||||
@ -85,6 +85,6 @@ function test()
|
|||||||
isnot(URL.indexOf("toolbox.js"), -1, "we have the expected view source URL");
|
isnot(URL.indexOf("toolbox.js"), -1, "we have the expected view source URL");
|
||||||
is(URL.indexOf("->"), -1, "no -> in the URL given to view-source");
|
is(URL.indexOf("->"), -1, "no -> in the URL given to view-source");
|
||||||
|
|
||||||
browserconsole.viewSource = viewSource;
|
browserconsole.viewSourceInDebugger = viewSource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ function test()
|
|||||||
{
|
{
|
||||||
let viewSource = hud.viewSource;
|
let viewSource = hud.viewSource;
|
||||||
let viewSourceCalled = false;
|
let viewSourceCalled = false;
|
||||||
hud.viewSource = () => viewSourceCalled = true;
|
hud.viewSourceInDebugger = () => viewSourceCalled = true;
|
||||||
|
|
||||||
for (let result of results) {
|
for (let result of results) {
|
||||||
viewSourceCalled = false;
|
viewSourceCalled = false;
|
||||||
@ -67,7 +67,7 @@ function test()
|
|||||||
ok(viewSourceCalled, "view source opened");
|
ok(viewSourceCalled, "view source opened");
|
||||||
}
|
}
|
||||||
|
|
||||||
hud.viewSource = viewSource;
|
hud.viewSourceInDebugger = viewSource;
|
||||||
finishTest();
|
finishTest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,10 @@ const prefs = {
|
|||||||
"error",
|
"error",
|
||||||
"warn",
|
"warn",
|
||||||
"info",
|
"info",
|
||||||
"log"
|
"log",
|
||||||
|
"serviceworkers",
|
||||||
|
"sharedworkers",
|
||||||
|
"windowlessworkers"
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
// Tests that the basic console.log()-style APIs and filtering work for sharedWorkers
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-workers.html";
|
||||||
|
|
||||||
|
function pushPrefEnv()
|
||||||
|
{
|
||||||
|
let deferred = promise.defer();
|
||||||
|
let options = {'set': [["dom.workers.sharedWorkers.enabled", true]]};
|
||||||
|
SpecialPowers.pushPrefEnv(options, deferred.resolve);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
let test = asyncTest(function*() {
|
||||||
|
yield pushPrefEnv();
|
||||||
|
yield loadTab(TEST_URI);
|
||||||
|
|
||||||
|
let hud = yield openConsole();
|
||||||
|
|
||||||
|
yield waitForMessages({
|
||||||
|
webconsole: hud,
|
||||||
|
messages: [{
|
||||||
|
text: "foo-bar-shared-worker"
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
hud.setFilterState('sharedworkers', false);
|
||||||
|
|
||||||
|
is(hud.outputNode.querySelectorAll(".filtered-by-type").length, 1,
|
||||||
|
"1 message hidden for sharedworkers (logging turned off)")
|
||||||
|
|
||||||
|
hud.setFilterState('sharedworkers', true);
|
||||||
|
|
||||||
|
is(hud.outputNode.querySelectorAll(".filtered-by-type").length, 0,
|
||||||
|
"1 message shown for sharedworkers (logging turned on)")
|
||||||
|
|
||||||
|
hud.setFilterState('sharedworkers', false);
|
||||||
|
|
||||||
|
hud.jsterm.clearOutput(true);
|
||||||
|
});
|
13
browser/devtools/webconsole/test/test-console-workers.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!-- Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Console test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var sw = new SharedWorker('data:application/javascript,console.log("foo-bar-shared-worker");');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
const {Cc, Ci, Cu} = require("chrome");
|
const {Cc, Ci, Cu} = require("chrome");
|
||||||
|
|
||||||
let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
|
const {Utils: WebConsoleUtils, CONSOLE_WORKER_IDS} = require("devtools/toolkit/webconsole/utils");
|
||||||
|
|
||||||
loader.lazyServiceGetter(this, "clipboardHelper",
|
loader.lazyServiceGetter(this, "clipboardHelper",
|
||||||
"@mozilla.org/widget/clipboardhelper;1",
|
"@mozilla.org/widget/clipboardhelper;1",
|
||||||
@ -138,6 +138,10 @@ const LEVELS = {
|
|||||||
count: SEVERITY_LOG
|
count: SEVERITY_LOG
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This array contains the prefKey for the workers and it must keep them in the
|
||||||
|
// same order as CONSOLE_WORKER_IDS
|
||||||
|
const WORKERTYPES_PREFKEYS = [ 'sharedworkers', 'serviceworkers', 'windowlessworkers' ];
|
||||||
|
|
||||||
// The lowest HTTP response code (inclusive) that is considered an error.
|
// The lowest HTTP response code (inclusive) that is considered an error.
|
||||||
const MIN_HTTP_ERROR_CODE = 400;
|
const MIN_HTTP_ERROR_CODE = 400;
|
||||||
// The highest HTTP response code (inclusive) that is considered an error.
|
// The highest HTTP response code (inclusive) that is considered an error.
|
||||||
@ -649,7 +653,8 @@ WebConsoleFrame.prototype = {
|
|||||||
{
|
{
|
||||||
let prefs = ["network", "networkinfo", "csserror", "cssparser", "csslog",
|
let prefs = ["network", "networkinfo", "csserror", "cssparser", "csslog",
|
||||||
"exception", "jswarn", "jslog", "error", "info", "warn", "log",
|
"exception", "jswarn", "jslog", "error", "info", "warn", "log",
|
||||||
"secerror", "secwarn", "netwarn", "netxhr"];
|
"secerror", "secwarn", "netwarn", "netxhr", "sharedworkers",
|
||||||
|
"serviceworkers", "windowlessworkers"];
|
||||||
for (let pref of prefs) {
|
for (let pref of prefs) {
|
||||||
this.filterPrefs[pref] = Services.prefs
|
this.filterPrefs[pref] = Services.prefs
|
||||||
.getBoolPref(this._filterPrefsPrefix + pref);
|
.getBoolPref(this._filterPrefsPrefix + pref);
|
||||||
@ -1026,8 +1031,11 @@ WebConsoleFrame.prototype = {
|
|||||||
// (filter="error", filter="cssparser", etc.) and add or remove the
|
// (filter="error", filter="cssparser", etc.) and add or remove the
|
||||||
// "filtered-by-type" class, which turns on or off the display.
|
// "filtered-by-type" class, which turns on or off the display.
|
||||||
|
|
||||||
|
let attribute = WORKERTYPES_PREFKEYS.indexOf(aPrefKey) == -1
|
||||||
|
? 'filter' : 'workerType';
|
||||||
|
|
||||||
let xpath = ".//*[contains(@class, 'message') and " +
|
let xpath = ".//*[contains(@class, 'message') and " +
|
||||||
"@filter='" + aPrefKey + "']";
|
"@" + attribute + "='" + aPrefKey + "']";
|
||||||
let result = doc.evaluate(xpath, outputNode, null,
|
let result = doc.evaluate(xpath, outputNode, null,
|
||||||
Ci.nsIDOMXPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
|
Ci.nsIDOMXPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
|
||||||
for (let i = 0; i < result.snapshotLength; i++) {
|
for (let i = 0; i < result.snapshotLength; i++) {
|
||||||
@ -1088,6 +1096,12 @@ WebConsoleFrame.prototype = {
|
|||||||
isFiltered = true;
|
isFiltered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by worker type
|
||||||
|
if ("workerType" in aNode && !this.getFilterState(aNode.workerType)) {
|
||||||
|
aNode.classList.add("filtered-by-type");
|
||||||
|
isFiltered = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Filter on the search string.
|
// Filter on the search string.
|
||||||
let search = this.filterBox.value;
|
let search = this.filterBox.value;
|
||||||
let text = aNode.clipboardText;
|
let text = aNode.clipboardText;
|
||||||
@ -1365,6 +1379,12 @@ WebConsoleFrame.prototype = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let workerTypeID = CONSOLE_WORKER_IDS.indexOf(aMessage.workerType);
|
||||||
|
if (workerTypeID != -1) {
|
||||||
|
node.workerType = WORKERTYPES_PREFKEYS[workerTypeID];
|
||||||
|
node.setAttribute('workerType', WORKERTYPES_PREFKEYS[workerTypeID]);
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -2720,7 +2740,7 @@ WebConsoleFrame.prototype = {
|
|||||||
let onClick = () => {
|
let onClick = () => {
|
||||||
let target = locationNode.target;
|
let target = locationNode.target;
|
||||||
if (target == "scratchpad" || isScratchpad) {
|
if (target == "scratchpad" || isScratchpad) {
|
||||||
this.owner.viewSourceInScratchpad(url);
|
this.owner.viewSourceInScratchpad(url, line);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +161,13 @@ function goUpdateConsoleCommands() {
|
|||||||
prefKey="info"/>
|
prefKey="info"/>
|
||||||
<menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false"
|
<menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false"
|
||||||
prefKey="log"/>
|
prefKey="log"/>
|
||||||
|
<menuseparator />
|
||||||
|
<menuitem label="&btnConsoleSharedWorkers;" type="checkbox"
|
||||||
|
autocheck="false" prefKey="sharedworkers"/>
|
||||||
|
<menuitem label="&btnConsoleServiceWorkers;" type="checkbox"
|
||||||
|
autocheck="false" prefKey="serviceworkers"/>
|
||||||
|
<menuitem label="&btnConsoleWindowlessWorkers;" type="checkbox"
|
||||||
|
autocheck="false" prefKey="windowlessworkers"/>
|
||||||
</menupopup>
|
</menupopup>
|
||||||
</toolbarbutton>
|
</toolbarbutton>
|
||||||
</hbox>
|
</hbox>
|
||||||
|
@ -10,9 +10,11 @@ Cu.import("resource://gre/modules/FileUtils.jsm");
|
|||||||
Cu.import("resource://gre/modules/Task.jsm");
|
Cu.import("resource://gre/modules/Task.jsm");
|
||||||
|
|
||||||
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||||
|
const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||||
const {require} = devtools;
|
const {require} = devtools;
|
||||||
const promise = require("promise");
|
const promise = require("promise");
|
||||||
const {AppProjects} = require("devtools/app-manager/app-projects");
|
const {AppProjects} = require("devtools/app-manager/app-projects");
|
||||||
|
gDevTools.testing = true;
|
||||||
|
|
||||||
let TEST_BASE;
|
let TEST_BASE;
|
||||||
if (window.location === "chrome://browser/content/browser.xul") {
|
if (window.location === "chrome://browser/content/browser.xul") {
|
||||||
@ -33,6 +35,7 @@ Services.prefs.setCharPref("devtools.webide.templatesURL", TEST_BASE + "template
|
|||||||
Services.prefs.setCharPref("devtools.devices.url", TEST_BASE + "browser_devices.json");
|
Services.prefs.setCharPref("devtools.devices.url", TEST_BASE + "browser_devices.json");
|
||||||
|
|
||||||
SimpleTest.registerCleanupFunction(() => {
|
SimpleTest.registerCleanupFunction(() => {
|
||||||
|
gDevTools.testing = false;
|
||||||
Services.prefs.clearUserPref("devtools.webide.enabled");
|
Services.prefs.clearUserPref("devtools.webide.enabled");
|
||||||
Services.prefs.clearUserPref("devtools.webide.enableLocalRuntime");
|
Services.prefs.clearUserPref("devtools.webide.enableLocalRuntime");
|
||||||
Services.prefs.clearUserPref("devtools.webide.autoinstallADBHelper");
|
Services.prefs.clearUserPref("devtools.webide.autoinstallADBHelper");
|
||||||
|
@ -698,7 +698,6 @@
|
|||||||
@RESPATH@/browser/@PREF_DIR@/firefox.js
|
@RESPATH@/browser/@PREF_DIR@/firefox.js
|
||||||
@RESPATH@/browser/@PREF_DIR@/firefox-branding.js
|
@RESPATH@/browser/@PREF_DIR@/firefox-branding.js
|
||||||
@RESPATH@/greprefs.js
|
@RESPATH@/greprefs.js
|
||||||
@RESPATH@/defaults/autoconfig/platform.js
|
|
||||||
@RESPATH@/defaults/autoconfig/prefcalls.js
|
@RESPATH@/defaults/autoconfig/prefcalls.js
|
||||||
@RESPATH@/browser/defaults/profile/prefs.js
|
@RESPATH@/browser/defaults/profile/prefs.js
|
||||||
|
|
||||||
|
@ -72,6 +72,8 @@ InstallDirRegKey HKLM "Software\Mozilla\MaintenanceService" ""
|
|||||||
|
|
||||||
SetOverwrite on
|
SetOverwrite on
|
||||||
|
|
||||||
|
; serviceinstall.cpp also uses this key, in case the path is changed, update
|
||||||
|
; there too.
|
||||||
!define MaintUninstallKey \
|
!define MaintUninstallKey \
|
||||||
"Software\Microsoft\Windows\CurrentVersion\Uninstall\MozillaMaintenanceService"
|
"Software\Microsoft\Windows\CurrentVersion\Uninstall\MozillaMaintenanceService"
|
||||||
|
|
||||||
|
@ -69,20 +69,22 @@ addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-on
|
|||||||
addonInstallRestartButton=Restart Now
|
addonInstallRestartButton=Restart Now
|
||||||
addonInstallRestartButton.accesskey=R
|
addonInstallRestartButton.accesskey=R
|
||||||
|
|
||||||
# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4):
|
# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4, addonError-5):
|
||||||
# #1 is the add-on name, #2 is the host name, #3 is the application name
|
# #1 is the add-on name, #2 is the host name, #3 is the application name
|
||||||
# #4 is the application version
|
# #4 is the application version
|
||||||
addonError-1=The add-on could not be downloaded because of a connection failure on #2.
|
addonError-1=The add-on could not be downloaded because of a connection failure on #2.
|
||||||
addonError-2=The add-on from #2 could not be installed because it does not match the add-on #3 expected.
|
addonError-2=The add-on from #2 could not be installed because it does not match the add-on #3 expected.
|
||||||
addonError-3=The add-on downloaded from #2 could not be installed because it appears to be corrupt.
|
addonError-3=The add-on downloaded from #2 could not be installed because it appears to be corrupt.
|
||||||
addonError-4=#1 could not be installed because #3 cannot modify the needed file.
|
addonError-4=#1 could not be installed because #3 cannot modify the needed file.
|
||||||
|
addonError-5=#3 has prevented this site from installing an unverified add-on.
|
||||||
|
|
||||||
# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonErrorIncompatible, addonErrorBlocklisted):
|
# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonLocalError-5, addonErrorIncompatible, addonErrorBlocklisted):
|
||||||
# #1 is the add-on name, #3 is the application name, #4 is the application version
|
# #1 is the add-on name, #3 is the application name, #4 is the application version
|
||||||
addonLocalError-1=This add-on could not be installed because of a filesystem error.
|
addonLocalError-1=This add-on could not be installed because of a filesystem error.
|
||||||
addonLocalError-2=This add-on could not be installed because it does not match the add-on #3 expected.
|
addonLocalError-2=This add-on could not be installed because it does not match the add-on #3 expected.
|
||||||
addonLocalError-3=This add-on could not be installed because it appears to be corrupt.
|
addonLocalError-3=This add-on could not be installed because it appears to be corrupt.
|
||||||
addonLocalError-4=#1 could not be installed because #3 cannot modify the needed file.
|
addonLocalError-4=#1 could not be installed because #3 cannot modify the needed file.
|
||||||
|
addonLocalError-5=This add-on could not be installed because it has not been verified.
|
||||||
addonErrorIncompatible=#1 could not be installed because it is not compatible with #3 #4.
|
addonErrorIncompatible=#1 could not be installed because it is not compatible with #3 #4.
|
||||||
addonErrorBlocklisted=#1 could not be installed because it has a high risk of causing stability or security problems.
|
addonErrorBlocklisted=#1 could not be installed because it has a high risk of causing stability or security problems.
|
||||||
|
|
||||||
|
@ -55,6 +55,14 @@ previewTooltip.image.brokenImage=Could not load the image
|
|||||||
#LOCALIZATION NOTE: Used in the image preview tooltip when the image could not be loaded
|
#LOCALIZATION NOTE: Used in the image preview tooltip when the image could not be loaded
|
||||||
eventsTooltip.openInDebugger=Open in Debugger
|
eventsTooltip.openInDebugger=Open in Debugger
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (docsTooltip.visitMDN): Shown in the tooltip that displays
|
||||||
|
# help from MDN. This is a link to the complete MDN documentation page.
|
||||||
|
docsTooltip.visitMDN=Visit MDN page
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (docsTooltip.visitMDN): Shown in the docs tooltip when the MDN page
|
||||||
|
# could not be loaded (for example, because of a connectivity problem).
|
||||||
|
docsTooltip.loadDocsError=Could not load docs page.
|
||||||
|
|
||||||
# LOCALIZATION NOTE (inspector.collapsePane): This is the tooltip for the button
|
# LOCALIZATION NOTE (inspector.collapsePane): This is the tooltip for the button
|
||||||
# that collapses the right panel (rules, computed, box-model, etc...) in the
|
# that collapses the right panel (rules, computed, box-model, etc...) in the
|
||||||
# inspector UI.
|
# inspector UI.
|
||||||
|
@ -73,3 +73,4 @@ timeline.markerDetail.startStack=Stack at start:
|
|||||||
timeline.markerDetail.endStack=Stack at end:
|
timeline.markerDetail.endStack=Stack at end:
|
||||||
timeline.markerDetail.unknownFrame=<unknown location>
|
timeline.markerDetail.unknownFrame=<unknown location>
|
||||||
timeline.markerDetail.asyncStack=(Async: %S)
|
timeline.markerDetail.asyncStack=(Async: %S)
|
||||||
|
timeline.markerDetail.causeName=Cause:
|
||||||
|
@ -76,6 +76,16 @@
|
|||||||
<!ENTITY btnConsoleXhr "XHR">
|
<!ENTITY btnConsoleXhr "XHR">
|
||||||
<!ENTITY btnConsoleReflows "Reflows">
|
<!ENTITY btnConsoleReflows "Reflows">
|
||||||
|
|
||||||
|
<!-- LOCALIZATION NODE (btnConsoleSharedWorkers) the term "Shared Workers"
|
||||||
|
- should not be translated. -->
|
||||||
|
<!ENTITY btnConsoleSharedWorkers "Shared Workers">
|
||||||
|
<!-- LOCALIZATION NODE (btnConsoleServiceWorkers) the term "Service Workers"
|
||||||
|
- should not be translated. -->
|
||||||
|
<!ENTITY btnConsoleServiceWorkers "Service Workers">
|
||||||
|
<!-- LOCALIZATION NODE (btnConsoleWindowlessWorkers) the term "Workers"
|
||||||
|
- should not be translated. -->
|
||||||
|
<!ENTITY btnConsoleWindowlessWorkers "Add-on or Chrome Workers">
|
||||||
|
|
||||||
<!ENTITY filterOutput.placeholder "Filter output">
|
<!ENTITY filterOutput.placeholder "Filter output">
|
||||||
<!ENTITY btnClear.label "Clear">
|
<!ENTITY btnClear.label "Clear">
|
||||||
<!ENTITY btnClear.tooltip "Clear the Web Console output">
|
<!ENTITY btnClear.tooltip "Clear the Web Console output">
|
||||||
|
@ -20,7 +20,7 @@ def test(mod, path, entity = None):
|
|||||||
if mod == "extensions/spellcheck":
|
if mod == "extensions/spellcheck":
|
||||||
return "ignore"
|
return "ignore"
|
||||||
# browser
|
# browser
|
||||||
if (re.match(r"searchplugins\/.+\.xml", path):
|
if (re.match(r"searchplugins\/.+\.xml", path)):
|
||||||
return "ignore"
|
return "ignore"
|
||||||
return "error"
|
return "error"
|
||||||
if mod == "extensions/spellcheck":
|
if mod == "extensions/spellcheck":
|
||||||
|
@ -13,6 +13,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
Cu.import("resource://gre/modules/Task.jsm");
|
Cu.import("resource://gre/modules/Task.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils","resource://gre/modules/PlacesUtils.jsm");
|
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils","resource://gre/modules/PlacesUtils.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
|
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "ReadingList", "resource:///modules/readinglist/ReadingList.jsm");
|
XPCOMUtils.defineLazyModuleGetter(this, "ReadingList", "resource:///modules/readinglist/ReadingList.jsm");
|
||||||
@ -24,6 +25,7 @@ let ReaderParent = {
|
|||||||
|
|
||||||
MESSAGES: [
|
MESSAGES: [
|
||||||
"Reader:AddToList",
|
"Reader:AddToList",
|
||||||
|
"Reader:AddToPocket",
|
||||||
"Reader:ArticleGet",
|
"Reader:ArticleGet",
|
||||||
"Reader:FaviconRequest",
|
"Reader:FaviconRequest",
|
||||||
"Reader:ListStatusRequest",
|
"Reader:ListStatusRequest",
|
||||||
@ -44,7 +46,7 @@ let ReaderParent = {
|
|||||||
|
|
||||||
receiveMessage: function(message) {
|
receiveMessage: function(message) {
|
||||||
switch (message.name) {
|
switch (message.name) {
|
||||||
case "Reader:AddToList":
|
case "Reader:AddToList": {
|
||||||
let article = message.data.article;
|
let article = message.data.article;
|
||||||
ReadingList.getMetadataFromBrowser(message.target).then(function(metadata) {
|
ReadingList.getMetadataFromBrowser(message.target).then(function(metadata) {
|
||||||
if (metadata.previews.length > 0) {
|
if (metadata.previews.length > 0) {
|
||||||
@ -59,6 +61,25 @@ let ReaderParent = {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "Reader:AddToPocket": {
|
||||||
|
let doc = message.target.ownerDocument;
|
||||||
|
let pocketWidget = doc.getElementById("pocket-button");
|
||||||
|
let placement = CustomizableUI.getPlacementOfWidget("pocket-button");
|
||||||
|
if (placement) {
|
||||||
|
if (placement.area == CustomizableUI.AREA_PANEL) {
|
||||||
|
doc.defaultView.PanelUI.show().then(function() {
|
||||||
|
// The DOM node might not exist yet if the panel wasn't opened before.
|
||||||
|
pocketWidget = doc.getElementById("pocket-button");
|
||||||
|
pocketWidget.doCommand();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pocketWidget.doCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "Reader:ArticleGet":
|
case "Reader:ArticleGet":
|
||||||
this._getArticle(message.data.url, message.target).then((article) => {
|
this._getArticle(message.data.url, message.target).then((article) => {
|
||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 45 KiB |