Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2014-05-13 16:27:45 -04:00
commit 1170b7a391
117 changed files with 5311 additions and 515 deletions

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="de1c755411949b50ae395b42e124af215ed9b702"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="461ea9a09563c2caf1fe5195227d126dadb83dc5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="30f87e602509415be4f69493c23cba1912f91ef5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="ca283b9db2b151d465cfd2e19346cf58fe89e413"/>
@ -102,7 +102,7 @@
<project name="platform/external/iproute2" path="external/iproute2" revision="c66c5716d5335e450f7a7b71ccc6a604fb2f41d2"/>
<project name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="d2685281e2e54ca14d1df304867aa82c37b27162"/>
<project name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="627f9b20fc518937b93747a7ff1ed4f5ed46e06f"/>
<project name="platform/prebuilts/tools" path="prebuilts/tools" revision="f19ba22ba6973804628781606bc072d18f3f79c2"/>
<project name="platform/prebuilts/tools" path="prebuilts/tools" revision="acba00cdb4596c6dcb61ed06f14cf4ec89623539"/>
<project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="431afac2ebfdd9c1c8402b413ff5914ed448e961"/>
<project name="android-sdk" path="sdk" remote="b2g" revision="4f46930827957afbce500a4a920755a218bf3155"/>
</manifest>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="de1c755411949b50ae395b42e124af215ed9b702"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="461ea9a09563c2caf1fe5195227d126dadb83dc5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="30f87e602509415be4f69493c23cba1912f91ef5"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3512d982f336887a73283bb8d1147a8f7b822077"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="65fba428f8d76336b33ddd9e15900357953600ba">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="de1c755411949b50ae395b42e124af215ed9b702"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="461ea9a09563c2caf1fe5195227d126dadb83dc5"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="30f87e602509415be4f69493c23cba1912f91ef5"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="de1c755411949b50ae395b42e124af215ed9b702"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="461ea9a09563c2caf1fe5195227d126dadb83dc5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="30f87e602509415be4f69493c23cba1912f91ef5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="ca283b9db2b151d465cfd2e19346cf58fe89e413"/>
@ -102,7 +102,7 @@
<project name="platform/external/iproute2" path="external/iproute2" revision="c66c5716d5335e450f7a7b71ccc6a604fb2f41d2"/>
<project name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="d2685281e2e54ca14d1df304867aa82c37b27162"/>
<project name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="627f9b20fc518937b93747a7ff1ed4f5ed46e06f"/>
<project name="platform/prebuilts/tools" path="prebuilts/tools" revision="f19ba22ba6973804628781606bc072d18f3f79c2"/>
<project name="platform/prebuilts/tools" path="prebuilts/tools" revision="acba00cdb4596c6dcb61ed06f14cf4ec89623539"/>
<project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="431afac2ebfdd9c1c8402b413ff5914ed448e961"/>
<project name="android-sdk" path="sdk" remote="b2g" revision="4f46930827957afbce500a4a920755a218bf3155"/>
</manifest>

View File

@ -18,7 +18,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="de1c755411949b50ae395b42e124af215ed9b702"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="461ea9a09563c2caf1fe5195227d126dadb83dc5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="30f87e602509415be4f69493c23cba1912f91ef5"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3512d982f336887a73283bb8d1147a8f7b822077"/>
@ -118,7 +118,7 @@
<default remote="caf" revision="jb_3.2" sync-j="4"/>
<!-- Flame specific things -->
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="e8a318f7690092e639ba88891606f4183e846d3f"/>
<project name="device/qcom/common" path="device/qcom/common" revision="66715ba03ef9dd277ebd65ef3c17231c4dcdf0ed"/>
<project name="device/qcom/common" path="device/qcom/common" revision="34ed8345250bb97262d70a052217a92e83444ede"/>
<project name="device-flame" path="device/t2m/flame" remote="b2g" revision="ecc08b2f0efea93c778e3525553bcd4bd5f2cf49"/>
<project name="kernel/msm" path="kernel" revision="7158567fc83e7475f08db3adedc5df1ad6f54abd"/>
<project name="platform/bootable/recovery" path="bootable/recovery" revision="f2914eacee9120680a41463708bb6ee8291749fc"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "d37aa6da234abefd5227511661ec6a365c1cfb84",
"revision": "5bd5379f114f542314571a0361089cfd5beb5827",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="de1c755411949b50ae395b42e124af215ed9b702"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="461ea9a09563c2caf1fe5195227d126dadb83dc5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="30f87e602509415be4f69493c23cba1912f91ef5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="de1c755411949b50ae395b42e124af215ed9b702"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="461ea9a09563c2caf1fe5195227d126dadb83dc5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="30f87e602509415be4f69493c23cba1912f91ef5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="de1c755411949b50ae395b42e124af215ed9b702"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="461ea9a09563c2caf1fe5195227d126dadb83dc5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="30f87e602509415be4f69493c23cba1912f91ef5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="de1c755411949b50ae395b42e124af215ed9b702"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="461ea9a09563c2caf1fe5195227d126dadb83dc5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="30f87e602509415be4f69493c23cba1912f91ef5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="de1c755411949b50ae395b42e124af215ed9b702"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="461ea9a09563c2caf1fe5195227d126dadb83dc5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="30f87e602509415be4f69493c23cba1912f91ef5"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3512d982f336887a73283bb8d1147a8f7b822077"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="de1c755411949b50ae395b42e124af215ed9b702"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="461ea9a09563c2caf1fe5195227d126dadb83dc5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="30f87e602509415be4f69493c23cba1912f91ef5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -1239,6 +1239,13 @@ pref("devtools.appmanager.enabled", true);
pref("devtools.appmanager.lastTab", "help");
pref("devtools.appmanager.manifestEditor.enabled", true);
// Enable devtools webide
#ifdef MOZ_DEVTOOLS_WEBIDE
pref("devtools.webide.enabled", true);
#else
pref("devtools.webide.enabled", false);
#endif
// Toolbox preferences
pref("devtools.toolbox.footer.height", 250);
pref("devtools.toolbox.sidebar.width", 500);
@ -1556,6 +1563,7 @@ pref("browser.cache.auto_delete_cache_version", 1);
pref("browser.cache.frecency_experiment", 0);
pref("browser.translation.detectLanguage", false);
pref("browser.translation.neverForLanguages", "");
// Telemetry experiments settings.
pref("experiments.enabled", true);

View File

@ -942,11 +942,11 @@ chatbox:-moz-full-screen-ancestor > .chat-titlebar {
%ifndef MOZ_WIDGET_GTK
#BMB_bookmarksPopup {
transform: scale(.7);
transform: scale(.4);
opacity: 0;
transition-property: transform, opacity;
transition-duration: 0.15s;
transition-timing-function: ease;
transition-timing-function: ease-out;
}
#BMB_bookmarksPopup[animate="open"] {
@ -972,12 +972,12 @@ chatbox:-moz-full-screen-ancestor > .chat-titlebar {
#BMB_bookmarksPopup[arrowposition="after_start"][animate="cancel"],
#BMB_bookmarksPopup[arrowposition="before_end"][animate="cancel"] {
transform: scale(.7) skew(30deg, 20deg);
transform: none;
}
#BMB_bookmarksPopup[arrowposition="after_end"][animate="cancel"],
#BMB_bookmarksPopup[arrowposition="before_start"][animate="cancel"] {
transform: scale(.7) skew(-30deg, -20deg);
transform: none;
}
%endif

View File

@ -906,6 +906,8 @@ let CustomizableUIInternal = {
let anchor = props.get("anchor");
if (anchor) {
aNode.setAttribute("cui-anchorid", anchor);
} else {
aNode.removeAttribute("cui-anchorid");
}
},

View File

@ -108,5 +108,6 @@ skip-if = os == "linux"
[browser_993322_widget_notoolbar.js]
[browser_995164_registerArea_during_customize_mode.js]
[browser_996364_registerArea_different_properties.js]
[browser_1008559_anchor_undo_restore.js]
[browser_bootstrapped_custom_toolbar.js]
[browser_panel_toggle.js]

View File

@ -0,0 +1,71 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const kAnchorAttribute = "cui-anchorid";
/**
* Check that anchor gets set correctly when moving an item from the panel to the toolbar
* using 'undo'
*/
add_task(function*() {
yield startCustomizing();
let button = document.getElementById("history-panelmenu");
is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button",
"Button (" + button.id + ") starts out with correct anchor");
let navbar = document.getElementById("nav-bar").customizationTarget;
simulateItemDrag(button, navbar);
is(CustomizableUI.getPlacementOfWidget(button.id).area, "nav-bar",
"Button (" + button.id + ") ends up in nav-bar");
ok(!button.hasAttribute(kAnchorAttribute),
"Button (" + button.id + ") has no anchor in toolbar");
let resetButton = document.getElementById("customization-reset-button");
ok(!resetButton.hasAttribute("disabled"), "Should be able to reset now.");
yield gCustomizeMode.reset();
is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button",
"Button (" + button.id + ") has anchor again");
let undoButton = document.getElementById("customization-undo-reset-button");
ok(!undoButton.hasAttribute("disabled"), "Should be able to undo now.");
yield gCustomizeMode.undoReset();
ok(!button.hasAttribute(kAnchorAttribute),
"Button (" + button.id + ") once again has no anchor in toolbar");
yield gCustomizeMode.reset();
yield endCustomizing();
});
/**
* Check that anchor gets set correctly when moving an item from the panel to the toolbar
* using 'reset'
*/
add_task(function*() {
yield startCustomizing();
let button = document.getElementById("bookmarks-menu-button");
ok(!button.hasAttribute(kAnchorAttribute),
"Button (" + button.id + ") has no anchor in toolbar");
let panel = document.getElementById("PanelUI-contents");
simulateItemDrag(button, panel);
is(CustomizableUI.getPlacementOfWidget(button.id).area, "PanelUI-contents",
"Button (" + button.id + ") ends up in panel");
is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button",
"Button (" + button.id + ") has correct anchor in the panel");
let resetButton = document.getElementById("customization-reset-button");
ok(!resetButton.hasAttribute("disabled"), "Should be able to reset now.");
yield gCustomizeMode.reset();
ok(!button.hasAttribute(kAnchorAttribute),
"Button (" + button.id + ") once again has no anchor in toolbar");
yield endCustomizing();
});

View File

@ -170,5 +170,16 @@ var gContentPane = {
{
document.documentElement.openSubDialog("chrome://browser/content/preferences/languages.xul",
"", null);
},
/**
* Displays the translation exceptions dialog where specific site and language
* translation preferences can be set.
*/
showTranslationExceptions: function ()
{
document.documentElement.openWindow("Browser:TranslationExceptions",
"chrome://browser/content/preferences/translation.xul",
"", null);
}
};

View File

@ -30,6 +30,11 @@
name="font.language.group"
type="wstring"
onchange="gContentPane._rebuildFonts();"/>
<!-- LANGUAGES -->
<preference id="browser.translation.detectLanguage"
name="browser.translation.detectLanguage"
type="bool"/>
</preferences>
<script type="application/javascript" src="chrome://mozapps/content/preferences/fontbuilder.js"/>
@ -125,13 +130,31 @@
<groupbox id="languagesGroup">
<caption label="&languages.label;"/>
<hbox id="languagesBox" align="center">
<description flex="1" control="chooseLanguage">&chooseLanguage.label;</description>
<button id="chooseLanguage"
label="&chooseButton.label;"
accesskey="&chooseButton.accesskey;"
oncommand="gContentPane.showLanguages();"/>
</hbox>
<grid id="languagesGrid">
<columns>
<column flex="1"/>
<column/>
</columns>
<rows id="languagesRows">
<row id="preferredLanguageRow">
<label flex="1" control="chooseLanguage">&chooseLanguage.label;</label>
<button id="chooseLanguage"
label="&chooseButton.label;"
accesskey="&chooseButton.accesskey;"
oncommand="gContentPane.showLanguages();"/>
</row>
<row hidden="true">
<checkbox id="translate" preference="browser.translation.detectLanguage" flex="1"
label="&translateWebPages.label;" accesskey="&translateWebPages.accesskey;"
onsyncfrompreference="return gContentPane.updateButtons('translateButton',
'browser.translation.detectLanguage');"/>
<button id="translateButton" label="&translateExceptions.label;"
oncommand="gContentPane.showTranslationExceptions();"
accesskey="&translateExceptions.accesskey;"/>
</row>
</rows>
</grid>
</groupbox>
</prefpane>

View File

@ -169,5 +169,15 @@ var gContentPane = {
{
openDialog("chrome://browser/content/preferences/languages.xul",
"Browser:LanguagePreferences", null);
},
/**
* Displays the translation exceptions dialog where specific site and language
* translation preferences can be set.
*/
showTranslationExceptions: function ()
{
openDialog("chrome://browser/content/preferences/translation.xul",
"Browser:TranslationExceptions", null);
}
};

View File

@ -14,6 +14,11 @@
name="font.language.group"
type="wstring"
onchange="gContentPane._rebuildFonts();"/>
<!-- Languages -->
<preference id="browser.translation.detectLanguage"
name="browser.translation.detectLanguage"
type="bool"/>
</preferences>
<script type="application/javascript"
@ -126,4 +131,14 @@
accesskey="&chooseButton.accesskey;"
oncommand="gContentPane.showLanguages();"/>
</hbox>
<hbox id="translationBox" hidden="true">
<checkbox id="translate" preference="browser.translation.detectLanguage" flex="1"
label="&translateWebPages.label;" accesskey="&translateWebPages.accesskey;"
onsyncfrompreference="return gContentPane.updateButtons('translateButton',
'browser.translation.detectLanguage');"/>
<button id="translateButton" label="&translateExceptions.label;"
oncommand="gContentPane.showTranslationExceptions();"
accesskey="&translateExceptions.accesskey;"/>
</hbox>
</groupbox>

View File

@ -45,3 +45,5 @@ browser.jar:
#endif
* content/browser/preferences/tabs.xul
* content/browser/preferences/tabs.js
* content/browser/preferences/translation.xul
content/browser/preferences/translation.js

View File

@ -0,0 +1,227 @@
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "gLangBundle", () =>
Services.strings.createBundle("chrome://global/locale/languageNames.properties"));
const kPermissionType = "translate";
const kLanguagesPref = "browser.translation.neverForLanguages";
function Tree(aId, aData)
{
this._data = aData;
this._tree = document.getElementById(aId);
this._tree.treeBoxObject.view = this;
}
Tree.prototype = {
get boxObject() this._tree.treeBoxObject,
get isEmpty() !this._data.length,
get hasSelection() this.selection.count > 0,
getSelectedItems: function() {
let result = [];
let rc = this.selection.getRangeCount();
for (let i = 0; i < rc; ++i) {
let min = {}, max = {};
this.selection.getRangeAt(i, min, max);
for (let j = min.value; j <= max.value; ++j)
result.push(this._data[j]);
}
return result;
},
// nsITreeView implementation
get rowCount() this._data.length,
getCellText: function (aRow, aColumn) this._data[aRow],
isSeparator: function(aIndex) false,
isSorted: function() false,
isContainer: function(aIndex) false,
setTree: function(aTree) {},
getImageSrc: function(aRow, aColumn) {},
getProgressMode: function(aRow, aColumn) {},
getCellValue: function(aRow, aColumn) {},
cycleHeader: function(column) {},
getRowProperties: function(row) "",
getColumnProperties: function(column) "",
getCellProperties: function(row, column) "",
QueryInterface: XPCOMUtils.generateQI([Ci.nsITreeView])
};
function Lang(aCode)
{
this.langCode = aCode;
this._label = gLangBundle.GetStringFromName(aCode);
}
Lang.prototype = {
toString: function() this._label
}
let gTranslationExceptions = {
onLoad: function() {
if (this._siteTree) {
// Re-using an open dialog, clear the old observers.
this.uninit();
}
// Load site permissions into an array.
this._sites = [];
let enumerator = Services.perms.enumerator;
while (enumerator.hasMoreElements()) {
let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
if (perm.type == kPermissionType &&
perm.capability == Services.perms.DENY_ACTION) {
this._sites.push(perm.host);
}
}
Services.obs.addObserver(this, "perm-changed", false);
this._sites.sort();
this._siteTree = new Tree("sitesTree", this._sites);
this.onSiteSelected();
this._langs = this.getLanguageExceptions();
Services.prefs.addObserver(kLanguagesPref, this, false);
this._langTree = new Tree("languagesTree", this._langs);
this.onLanguageSelected();
},
// Get the list of languages we don't translate as an array.
getLanguageExceptions: function() {
let langs = Services.prefs.getCharPref(kLanguagesPref);
if (!langs)
return [];
let result = langs.split(",").map(code => new Lang(code));
result.sort();
return result;
},
observe: function(aSubject, aTopic, aData) {
if (aTopic == "perm-changed") {
if (aData == "cleared") {
if (!this._sites.length)
return;
let removed = this._sites.splice(0, this._sites.length);
this._siteTree.boxObject.rowCountChanged(0, - removed.length);
}
else {
let perm = aSubject.QueryInterface(Ci.nsIPermission);
if (perm.type != kPermissionType)
return;
if (aData == "added") {
if (perm.capability != Services.perms.DENY_ACTION)
return;
this._sites.push(perm.host);
this._sites.sort();
let boxObject = this._siteTree.boxObject;
boxObject.rowCountChanged(0, 1);
boxObject.invalidate();
}
else if (aData == "deleted") {
let index = this._sites.indexOf(perm.host);
if (index == -1)
return;
this._sites.splice(index, 1);
this._siteTree.boxObject.rowCountChanged(index, -1);
this.onSiteSelected();
return;
}
}
this.onSiteSelected();
}
else if (aTopic == "nsPref:changed") {
this._langs = this.getLanguageExceptions();
let change = this._langs.length - this._langTree.rowCount;
this._langTree._data = this._langs;
let boxObject = this._langTree.boxObject;
if (change)
boxObject.rowCountChanged(0, change);
boxObject.invalidate();
this.onLanguageSelected();
}
},
_handleButtonDisabling: function(aTree, aIdPart) {
let empty = aTree.isEmpty;
document.getElementById("removeAll" + aIdPart + "s").disabled = empty;
document.getElementById("remove" + aIdPart).disabled =
empty || !aTree.hasSelection;
},
onLanguageSelected: function() {
this._handleButtonDisabling(this._langTree, "Language");
},
onSiteSelected: function() {
this._handleButtonDisabling(this._siteTree, "Site");
},
onLanguageDeleted: function() {
let langs = Services.prefs.getCharPref(kLanguagesPref);
if (!langs)
return;
let removed = this._langTree.getSelectedItems().map(l => l.langCode);
langs = langs.split(",").filter(l => removed.indexOf(l) == -1);
Services.prefs.setCharPref(kLanguagesPref, langs.join(","));
},
onAllLanguagesDeleted: function() {
Services.prefs.setCharPref(kLanguagesPref, "");
},
onSiteDeleted: function() {
let removedSites = this._siteTree.getSelectedItems();
for (let host of removedSites)
Services.perms.remove(host, kPermissionType);
},
onAllSitesDeleted: function() {
if (this._siteTree.isEmpty)
return;
let removedSites = this._sites.splice(0, this._sites.length);
this._siteTree.boxObject.rowCountChanged(0, -removedSites.length);
for (let host of removedSites)
Services.perms.remove(host, kPermissionType);
this.onSiteSelected();
},
onSiteKeyPress: function(aEvent) {
if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE)
this.onSiteDeleted();
},
onLanguageKeyPress: function() {
if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE)
this.onLanguageDeleted();
},
onWindowKeyPress: function(aEvent) {
if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE)
window.close();
},
uninit: function() {
Services.obs.removeObserver(this, "perm-changed");
Services.prefs.removeObserver(kLanguagesPref, this);
}
};

View File

@ -0,0 +1,90 @@
<?xml version="1.0"?>
# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
# 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/.
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/translation.dtd">
<window id="TranslationDialog" class="windowDialog"
windowtype="Browser:TranslationExceptions"
title="&window.title;"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
style="width: &window.width;;"
onload="gTranslationExceptions.onLoad();"
onunload="gTranslationExceptions.uninit();"
persist="screenX screenY width height"
onkeypress="gTranslationExceptions.onWindowKeyPress(event);">
<script src="chrome://browser/content/preferences/translation.js"/>
<stringbundle id="bundlePreferences"
src="chrome://browser/locale/preferences/preferences.properties"/>
<keyset>
<key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
</keyset>
<vbox class="contentPane" flex="1">
<label id="languagesLabel" control="permissionsTree">&noTranslationForLanguages.label;</label>
<separator class="thin"/>
<tree id="languagesTree" flex="1" style="height: 12em;"
hidecolumnpicker="true"
onkeypress="gTranslationExceptions.onLanguageKeyPress(event)"
onselect="gTranslationExceptions.onLanguageSelected();">
<treecols>
<treecol id="languageCol" label="&treehead.languageName.label;" flex="1"/>
</treecols>
<treechildren/>
</tree>
</vbox>
<hbox align="end">
<hbox class="actionButtons" flex="1">
<button id="removeLanguage" disabled="true"
accesskey="&removeLanguage.accesskey;"
icon="remove" label="&removeLanguage.label;"
oncommand="gTranslationExceptions.onLanguageDeleted();"/>
<button id="removeAllLanguages"
icon="clear" label="&removeAllLanguages.label;"
accesskey="&removeAllLanguages.accesskey;"
oncommand="gTranslationExceptions.onAllLanguagesDeleted();"/>
<spacer flex="1"/>
</hbox>
</hbox>
<separator/>
<vbox class="contentPane" flex="1">
<label id="languagesLabel" control="permissionsTree">&noTranslationForSites.label;</label>
<separator class="thin"/>
<tree id="sitesTree" flex="1" style="height: 12em;"
hidecolumnpicker="true"
onkeypress="gTranslationExceptions.onSiteKeyPress(event)"
onselect="gTranslationExceptions.onSiteSelected();">
<treecols>
<treecol id="siteCol" label="&treehead.siteName.label;" flex="1"/>
</treecols>
<treechildren/>
</tree>
</vbox>
<hbox align="end">
<hbox class="actionButtons" flex="1">
<button id="removeSite" disabled="true"
accesskey="&removeSite.accesskey;"
icon="remove" label="&removeSite.label;"
oncommand="gTranslationExceptions.onSiteDeleted();"/>
<button id="removeAllSites"
icon="clear" label="&removeAllSites.label;"
accesskey="&removeAllSites.accesskey;"
oncommand="gTranslationExceptions.onAllSitesDeleted();"/>
<spacer flex="1"/>
#ifndef XP_MACOSX
<button oncommand="close();" icon="close"
label="&button.close.label;" accesskey="&button.close.accesskey;"/>
#endif
</hbox>
<resizer type="window" dir="bottomend"/>
</hbox>
</window>

View File

@ -64,8 +64,6 @@ TranslationUI.prototype = {
STATE_TRANSLATED: 2,
STATE_ERROR: 3,
get doc() this.browser.contentDocument,
translate: function(aFrom, aTo) {
this.state = this.STATE_TRANSLATING;
this.translatedFrom = aFrom;
@ -125,6 +123,18 @@ TranslationUI.prototype = {
return notif;
},
shouldShowInfoBar: function(aURI, aDetectedLanguage) {
// Check if we should never show the infobar for this language.
let neverForLangs =
Services.prefs.getCharPref("browser.translation.neverForLanguages");
if (neverForLangs.split(",").indexOf(aDetectedLanguage) != -1)
return false;
// or if we should never show the infobar for this domain.
let perms = Services.perms;
return perms.testExactPermission(aURI, "translate") != perms.DENY_ACTION;
},
showTranslationUI: function(aDetectedLanguage) {
this.detectedLanguage = aDetectedLanguage;
@ -135,6 +145,10 @@ TranslationUI.prototype = {
this.originalShown = true;
this.showURLBarIcon();
if (!this.shouldShowInfoBar(this.browser.currentURI, aDetectedLanguage))
return null;
return this.showTranslationInfoBar();
}
};

View File

@ -1,3 +1,4 @@
[DEFAULT]
[browser_translation_infobar.js]
[browser_translation_exceptions.js]

View File

@ -0,0 +1,301 @@
/* 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/. */
// tests the translation infobar, using a fake 'Translation' implementation.
Components.utils.import("resource:///modules/translation/Translation.jsm");
const kLanguagesPref = "browser.translation.neverForLanguages";
function test() {
waitForExplicitFinish();
let tab = gBrowser.addTab();
gBrowser.selectedTab = tab;
registerCleanupFunction(function () {
gBrowser.removeTab(tab);
});
tab.linkedBrowser.addEventListener("load", function onload() {
tab.linkedBrowser.removeEventListener("load", onload, true);
Task.spawn(function* () {
for (let test of gTests) {
info(test.desc);
yield test.run();
}
}).then(finish, ex => {
ok(false, "Unexpected Exception: " + ex);
finish();
});
}, true);
content.location = "http://example.com/";
}
function getLanguageExceptions() {
let langs = Services.prefs.getCharPref(kLanguagesPref);
return langs ? langs.split(",") : [];
}
function getDomainExceptions() {
let results = [];
let enumerator = Services.perms.enumerator;
while (enumerator.hasMoreElements()) {
let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
if (perm.type == "translate" &&
perm.capability == Services.perms.DENY_ACTION)
results.push(perm.host);
}
return results;
}
function getInfoBar() {
return gBrowser.getNotificationBox().getNotificationWithValue("translation");
}
function openPopup(aPopup) {
let deferred = Promise.defer();
aPopup.addEventListener("popupshown", function popupShown() {
aPopup.removeEventListener("popupshown", popupShown);
deferred.resolve();
});
aPopup.focus();
// One down event to open the popup.
EventUtils.synthesizeKey("VK_DOWN",
{ altKey: !navigator.platform.contains("Mac") });
return deferred.promise;
}
function waitForWindowLoad(aWin) {
let deferred = Promise.defer();
aWin.addEventListener("load", function onload() {
aWin.removeEventListener("load", onload, true);
deferred.resolve();
}, true);
return deferred.promise;
}
let gTests = [
{
desc: "clean exception lists at startup",
run: function checkNeverForLanguage() {
is(getLanguageExceptions().length, 0,
"we start with an empty list of languages to never translate");
is(getDomainExceptions().length, 0,
"we start with an empty list of sites to never translate");
}
},
{
desc: "never for language",
run: function* checkNeverForLanguage() {
// Show the infobar for example.com and fr.
Translation.languageDetected(gBrowser.selectedBrowser, "fr");
let notif = getInfoBar();
ok(notif, "the infobar is visible");
let ui = gBrowser.selectedBrowser.translationUI;
let uri = gBrowser.selectedBrowser.currentURI;
ok(ui.shouldShowInfoBar(uri, "fr"),
"check shouldShowInfoBar initially returns true");
// Open the "options" drop down.
yield openPopup(notif._getAnonElt("options"));
ok(notif._getAnonElt("options").getAttribute("open"),
"the options menu is open");
// Check that the item is not disabled.
ok(!notif._getAnonElt("neverForLanguage").disabled,
"The 'Never translate <language>' item isn't disabled");
// Click the 'Never for French' item.
notif._getAnonElt("neverForLanguage").click();
ok(!getInfoBar(), "infobar hidden");
// Check this has been saved to the exceptions list.
let langs = getLanguageExceptions();
is(langs.length, 1, "one language in the exception list");
is(langs[0], "fr", "correct language in the exception list");
ok(!ui.shouldShowInfoBar(uri, "fr"),
"the infobar wouldn't be shown anymore");
// Reopen the infobar.
PopupNotifications.getNotification("translate").anchorElement.click();
notif = getInfoBar();
// Open the "options" drop down.
yield openPopup(notif._getAnonElt("options"));
ok(notif._getAnonElt("neverForLanguage").disabled,
"The 'Never translate French' item is disabled");
// Cleanup.
Services.prefs.setCharPref(kLanguagesPref, "");
notif.close();
}
},
{
desc: "never for site",
run: function* checkNeverForSite() {
// Show the infobar for example.com and fr.
Translation.languageDetected(gBrowser.selectedBrowser, "fr");
let notif = getInfoBar();
ok(notif, "the infobar is visible");
let ui = gBrowser.selectedBrowser.translationUI;
let uri = gBrowser.selectedBrowser.currentURI;
ok(ui.shouldShowInfoBar(uri, "fr"),
"check shouldShowInfoBar initially returns true");
// Open the "options" drop down.
yield openPopup(notif._getAnonElt("options"));
ok(notif._getAnonElt("options").getAttribute("open"),
"the options menu is open");
// Check that the item is not disabled.
ok(!notif._getAnonElt("neverForSite").disabled,
"The 'Never translate site' item isn't disabled");
// Click the 'Never for French' item.
notif._getAnonElt("neverForSite").click();
ok(!getInfoBar(), "infobar hidden");
// Check this has been saved to the exceptions list.
let sites = getDomainExceptions();
is(sites.length, 1, "one site in the exception list");
is(sites[0], "example.com", "correct site in the exception list");
ok(!ui.shouldShowInfoBar(uri, "fr"),
"the infobar wouldn't be shown anymore");
// Reopen the infobar.
PopupNotifications.getNotification("translate").anchorElement.click();
notif = getInfoBar();
// Open the "options" drop down.
yield openPopup(notif._getAnonElt("options"));
ok(notif._getAnonElt("neverForSite").disabled,
"The 'Never translate French' item is disabled");
// Cleanup.
Services.perms.remove("example.com", "translate");
notif.close();
}
},
{
desc: "language exception list",
run: function* checkLanguageExceptions() {
// Put 2 languages in the pref before opening the window to check
// the list is displayed on load.
Services.prefs.setCharPref(kLanguagesPref, "fr,de");
// Open the translation exceptions dialog.
let win = openDialog("chrome://browser/content/preferences/translation.xul",
"Browser:TranslationExceptions",
"", null);
yield waitForWindowLoad(win);
// Check that the list of language exceptions is loaded.
let getById = win.document.getElementById.bind(win.document);
let tree = getById("languagesTree");
let remove = getById("removeLanguage");
let removeAll = getById("removeAllLanguages");
is(tree.view.rowCount, 2, "The language exceptions list has 2 items");
ok(remove.disabled, "The 'Remove Language' button is disabled");
ok(!removeAll.disabled, "The 'Remove All Languages' button is enabled");
// Select the first item.
tree.view.selection.select(0);
ok(!remove.disabled, "The 'Remove Language' button is enabled");
// Click the 'Remove' button.
remove.click();
is(tree.view.rowCount, 1, "The language exceptions now contains 1 item");
is(getLanguageExceptions().length, 1, "One exception in the pref");
// Clear the pref, and check the last item is removed from the display.
Services.prefs.setCharPref(kLanguagesPref, "");
is(tree.view.rowCount, 0, "The language exceptions list is empty");
ok(remove.disabled, "The 'Remove Language' button is disabled");
ok(removeAll.disabled, "The 'Remove All Languages' button is disabled");
// Add an item and check it appears.
Services.prefs.setCharPref(kLanguagesPref, "fr");
is(tree.view.rowCount, 1, "The language exceptions list has 1 item");
ok(remove.disabled, "The 'Remove Language' button is disabled");
ok(!removeAll.disabled, "The 'Remove All Languages' button is enabled");
// Click the 'Remove All' button.
removeAll.click();
is(tree.view.rowCount, 0, "The language exceptions list is empty");
ok(remove.disabled, "The 'Remove Language' button is disabled");
ok(removeAll.disabled, "The 'Remove All Languages' button is disabled");
is(Services.prefs.getCharPref(kLanguagesPref), "", "The pref is empty");
win.close();
}
},
{
desc: "domains exception list",
run: function* checkDomainExceptions() {
// Put 2 exceptions before opening the window to check the list is
// displayed on load.
let perms = Services.perms;
perms.add(makeURI("http://example.org"), "translate", perms.DENY_ACTION);
perms.add(makeURI("http://example.com"), "translate", perms.DENY_ACTION);
// Open the translation exceptions dialog.
let win = openDialog("chrome://browser/content/preferences/translation.xul",
"Browser:TranslationExceptions",
"", null);
yield waitForWindowLoad(win);
// Check that the list of language exceptions is loaded.
let getById = win.document.getElementById.bind(win.document);
let tree = getById("sitesTree");
let remove = getById("removeSite");
let removeAll = getById("removeAllSites");
is(tree.view.rowCount, 2, "The sites exceptions list has 2 items");
ok(remove.disabled, "The 'Remove Site' button is disabled");
ok(!removeAll.disabled, "The 'Remove All Sites' button is enabled");
// Select the first item.
tree.view.selection.select(0);
ok(!remove.disabled, "The 'Remove Site' button is enabled");
// Click the 'Remove' button.
remove.click();
is(tree.view.rowCount, 1, "The site exceptions now contains 1 item");
is(getDomainExceptions().length, 1, "One exception in the permissions");
// Clear the permissions, and check the last item is removed from the display.
perms.remove("example.org", "translate");
perms.remove("example.com", "translate");
is(tree.view.rowCount, 0, "The site exceptions list is empty");
ok(remove.disabled, "The 'Remove Site' button is disabled");
ok(removeAll.disabled, "The 'Remove All Site' button is disabled");
// Add an item and check it appears.
perms.add(makeURI("http://example.com"), "translate", perms.DENY_ACTION);
is(tree.view.rowCount, 1, "The site exceptions list has 1 item");
ok(remove.disabled, "The 'Remove Site' button is disabled");
ok(!removeAll.disabled, "The 'Remove All Sites' button is enabled");
// Click the 'Remove All' button.
removeAll.click();
is(tree.view.rowCount, 0, "The site exceptions list is empty");
ok(remove.disabled, "The 'Remove Site' button is disabled");
ok(removeAll.disabled, "The 'Remove All Sites' button is disabled");
is(getDomainExceptions().length, 0, "No exceptions in the permissions");
win.close();
}
}
];

View File

@ -73,8 +73,19 @@
</xul:deck>
<xul:spacer flex="1"/>
<xul:button type="menu" label="&translation.options.menu;">
<xul:menupopup/>
<xul:button type="menu" anonid="options" label="&translation.options.menu;">
<xul:menupopup onpopupshowing="document.getBindingParent(this).optionsShowing();">
<xul:menuitem anonid="neverForLanguage"
oncommand="document.getBindingParent(this).neverForLanguage();"/>
<xul:menuitem anonid="neverForSite"
oncommand="document.getBindingParent(this).neverForSite();"
label="&translation.options.neverForSite.label;"
accesskey="&translation.options.neverForSite.accesskey;"/>
<xul:menuseparator/>
<xul:menuitem oncommand="openPreferences('paneContent');"
label="&translation.options.preferences.label;"
accesskey="&translation.options.preferences.accesskey;"/>
</xul:menupopup>
</xul:button>
</xul:hbox>
@ -181,6 +192,80 @@
</body>
</method>
<method name="optionsShowing">
<body>
<![CDATA[
// Get the source language name.
let lang;
if (this.state == this.translation.STATE_OFFER)
lang = this._getAnonElt("detectedLanguage").value;
else
lang = this._getAnonElt("fromLanguage").value;
let langBundle =
Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Ci.nsIStringBundleService)
.createBundle("chrome://global/locale/languageNames.properties");
let langName = langBundle.GetStringFromName(lang);
// Set the label and accesskey on the menuitem.
let bundle =
Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Ci.nsIStringBundleService)
.createBundle("chrome://browser/locale/translation.properties");
let item = this._getAnonElt("neverForLanguage");
const kStrId = "translation.options.neverForLanguage";
item.setAttribute("label",
bundle.formatStringFromName(kStrId + ".label",
[langName], 1));
item.setAttribute("accesskey",
bundle.GetStringFromName(kStrId + ".accesskey"));
item.langCode = lang;
// We may need to disable the menuitems if they have already been used.
// Check if translation is already disabled for this language:
let neverForLangs =
Services.prefs.getCharPref("browser.translation.neverForLanguages");
item.disabled = neverForLangs.split(",").indexOf(lang) != -1;
// Check if translation is disabled for the domain:
let uri = this.translation.browser.currentURI;
let perms = Services.perms;
item = this._getAnonElt("neverForSite");
item.disabled =
perms.testExactPermission(uri, "translate") == perms.DENY_ACTION;
]]>
</body>
</method>
<method name="neverForLanguage">
<body>
<![CDATA[
const kPrefName = "browser.translation.neverForLanguages";
let val = Services.prefs.getCharPref(kPrefName);
if (val)
val += ",";
val += this._getAnonElt("neverForLanguage").langCode;
Services.prefs.setCharPref(kPrefName, val);
this.close();
]]>
</body>
</method>
<method name="neverForSite">
<body>
<![CDATA[
let uri = this.translation.browser.currentURI;
let perms = Services.perms;
perms.add(uri, "translate", perms.DENY_ACTION);
this.close();
]]>
</body>
</method>
</implementation>
</binding>
</bindings>

View File

@ -1,9 +1,10 @@
const {Cc,Ci,Cu} = require("chrome");
const {Cc,Ci,Cu,Cr} = require("chrome");
const ObservableObject = require("devtools/shared/observable-object");
const promise = require("devtools/toolkit/deprecated-sync-thenables");
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js");
const {generateUUID} = Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator);
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
/**
* IndexedDB wrapper that just save project objects
@ -37,20 +38,45 @@ const IDB = {
let db = IDB._db = request.result;
let objectStore = db.transaction("projects").objectStore("projects");
let projects = []
let toRemove = [];
objectStore.openCursor().onsuccess = function(event) {
let cursor = event.target.result;
if (cursor) {
if (cursor.value.location) {
// We need to make sure this object has a `.location` property.
// The UI depends on this property.
// This should not be needed as we make sure to register valid
// projects, but in the past (before bug 924568), we might have
// registered invalid objects.
projects.push(cursor.value);
// We also want to make sure the location is valid.
// If the location doesn't exist, we remove the project.
try {
let file = FileUtils.File(cursor.value.location);
if (file.exists()) {
projects.push(cursor.value);
} else {
toRemove.push(cursor.value.location);
}
} catch (e) {
if (e.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH) {
// A URL
projects.push(cursor.value);
}
}
}
cursor.continue();
} else {
deferred.resolve(projects);
let removePromises = [];
for (let location of toRemove) {
removePromises.push(IDB.remove(location));
}
promise.all(removePromises).then(() => {
deferred.resolve(projects);
});
}
};
};
@ -83,6 +109,12 @@ const IDB = {
update: function(project) {
let deferred = promise.defer();
// Clone object to make it storable by IndexedDB.
// Projects are proxified objects (for the template
// mechanismn in the first version of the App Manager).
// This will change in the future.
project = JSON.parse(JSON.stringify(project));
var transaction = IDB._db.transaction(["projects"], "readwrite");
var objectStore = transaction.objectStore("projects");
var request = objectStore.put(project);
@ -131,6 +163,14 @@ const AppProjects = {
},
addPackaged: function(folder) {
let file = FileUtils.File(folder.path);
if (!file.exists()) {
return promise.reject("path doesn't exist");
}
let existingProject = this.get(folder.path);
if (existingProject) {
return promise.reject("Already added");
}
let project = {
type: "packaged",
location: folder.path,
@ -151,6 +191,10 @@ const AppProjects = {
},
addHosted: function(manifestURL) {
let existingProject = this.get(manifestURL);
if (existingProject) {
return promise.reject("Already added");
}
let project = {
type: "hosted",
location: manifestURL
@ -163,11 +207,7 @@ const AppProjects = {
},
update: function (project) {
return IDB.update({
type: project.type,
location: project.location,
packagedAppOrigin: project.packagedAppOrigin
}).then(() => project);
return IDB.update(project);
},
remove: function(location) {

View File

@ -56,6 +56,7 @@ AppValidator.prototype._fetchManifest = function (manifestURL) {
this.manifestURL = manifestURL;
let req = new XMLHttpRequest();
req.overrideMimeType('text/plain');
try {
req.open("GET", manifestURL, true);
} catch(e) {
@ -155,6 +156,7 @@ AppValidator.prototype.validateLaunchPath = function (manifest) {
}
let req = new XMLHttpRequest();
req.overrideMimeType('text/plain');
try {
req.open("HEAD", indexURL, true);
} catch(e) {

View File

@ -522,7 +522,17 @@ let gDevToolsBrowser = {
* Open the App Manager
*/
openAppManager: function(gBrowser) {
gBrowser.selectedTab = gBrowser.addTab("about:app-manager");
if (Services.prefs.getBoolPref("devtools.webide.enabled")) {
let win = Services.wm.getMostRecentWindow("devtools:webide");
if (win) {
win.focus();
} else {
let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
ww.openWindow(null, "chrome://webide/content/", "webide", "chrome,centerscreen,resizable", null);
}
} else {
gBrowser.selectedTab = gBrowser.addTab("about:app-manager");
}
},
/**

View File

@ -29,6 +29,9 @@ DIRS += [
'webconsole',
]
if CONFIG['MOZ_DEVTOOLS_WEBIDE']:
DIRS += ['webide']
EXTRA_COMPONENTS += [
'devtools-clhandler.js',
'devtools-clhandler.manifest',

2
browser/devtools/webconsole/test/browser.ini Executable file → Normal file
View File

@ -61,6 +61,7 @@ support-files =
test-bug-859170-longstring-hang.html
test-bug-869003-iframe.html
test-bug-869003-top-window.html
test-closure-optimized-out.html
test-closures.html
test-console-assert.html
test-console-count.html
@ -137,6 +138,7 @@ support-files =
[browser_console_native_getters.js]
[browser_console_navigation_marker.js]
[browser_console_nsiconsolemessage.js]
[browser_console_optimized_out_vars.js]
[browser_console_private_browsing.js]
[browser_console_variables_view.js]
[browser_console_variables_view_while_debugging.js]

View File

@ -0,0 +1,82 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Check that inspecting an optimized out variable works when execution is
// paused.
function test() {
Task.spawn(function* () {
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-closure-optimized-out.html";
let {tab} = yield loadTab(TEST_URI);
let hud = yield openConsole(tab);
let { toolbox, panel, panelWin } = yield openDebugger();
yield waitForThreadEvents(panel, "resumed");
ok(true, "Debugger resumed");
let sources = panelWin.DebuggerView.Sources;
yield panel.addBreakpoint({ url: sources.values[0], line: 18 });
yield ensureThreadClientState(panel, "resumed");
let fetchedScopes = panelWin.once(panelWin.EVENTS.FETCHED_SCOPES);
let button = content.document.querySelector("button");
ok(button, "Button element found");
// Spin the event loop before causing the debuggee to pause, to allow
// this function to return first.
executeSoon(() => button.click());
let packet = yield fetchedScopes;
ok(true, "Scopes were fetched");
yield toolbox.selectTool("webconsole");
// This is the meat of the test: evaluate the optimized out variable.
hud.jsterm.execute("upvar");
yield waitForMessages({
webconsole: hud,
messages: [{
text: "optimized out",
category: CATEGORY_OUTPUT,
}]
});
finishTest();
}).then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
}
// Debugger helper functions stolen from browser/devtools/debugger/test/head.js.
function ensureThreadClientState(aPanel, aState) {
let thread = aPanel.panelWin.gThreadClient;
let state = thread.state;
info("Thread is: '" + state + "'.");
if (state == aState) {
return promise.resolve(null);
} else {
return waitForThreadEvents(aPanel, aState);
}
}
function waitForThreadEvents(aPanel, aEventName, aEventRepeat = 1) {
info("Waiting for thread event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
let deferred = promise.defer();
let thread = aPanel.panelWin.gThreadClient;
let count = 0;
thread.addListener(aEventName, function onEvent(aEventName, ...aArgs) {
info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s).");
if (count == aEventRepeat) {
ok(true, "Enough '" + aEventName + "' thread events have been fired.");
thread.removeListener(aEventName, onEvent);
deferred.resolve.apply(deferred, aArgs);
}
});
return deferred.promise;
}

View File

@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset='utf-8'/>
<title>Debugger Test for Inspecting Optimized-Out Variables</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="text/javascript">
window.addEventListener("load", function onload() {
window.removeEventListener("load", onload);
function clickHandler(event) {
button.removeEventListener("click", clickHandler, false);
function outer(arg) {
var upvar = arg * 2;
// The inner lambda only aliases arg, so the frontend alias analysis decides
// that upvar is not aliased and is not in the CallObject.
return function () {
arg += 2;
};
}
var f = outer(42);
f();
}
var button = document.querySelector("button");
button.addEventListener("click", clickHandler, false);
});
</script>
</head>
<body>
<button>Click me!</button>
</body>
</html>

View File

@ -104,7 +104,7 @@ function goUpdateConsoleCommands() {
</toolbarbutton>
<toolbarbutton label="&btnPageCSS.label;" type="menu-button"
category="css" class="devtools-toolbarbutton webconsole-filter-button"
tooltiptext="&btnPageCSS.tooltip;"
tooltiptext="&btnPageCSS.tooltip2;"
accesskey="&btnPageCSS.accesskey;"
tabindex="4">
<menupopup>

View File

@ -0,0 +1,10 @@
# 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/.
PREF_JS_EXPORTS = $(srcdir)/webide-prefs.js
include $(topsrcdir)/config/rules.mk
libs::
$(NSINSTALL) $(srcdir)/modules/*.js $(FINAL_TARGET)/modules/devtools

View File

@ -0,0 +1,122 @@
/* 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/. */
const Cu = Components.utils;
Cu.import("resource:///modules/devtools/gDevTools.jsm");
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
const {AppProjects} = require("devtools/app-manager/app-projects");
const {AppValidator} = require("devtools/app-manager/app-validator");
const {AppManager} = require("devtools/app-manager");
window.addEventListener("load", function onLoad() {
window.removeEventListener("load", onLoad);
document.addEventListener("visibilitychange", updateUI, true);
AppManager.on("app-manager-update", onAppManagerUpdate);
updateUI();
}, true);
window.addEventListener("unload", function onUnload() {
window.removeEventListener("unload", onUnload);
AppManager.off("app-manager-update", onAppManagerUpdate);
}, true);
function onAppManagerUpdate(event, what, details) {
if (what == "project" ||
what == "project-validated") {
updateUI();
}
}
function resetUI() {
document.querySelector("#toolbar").classList.add("hidden");
document.querySelector("#type").classList.add("hidden");
document.querySelector("#descriptionHeader").classList.add("hidden");
document.querySelector("#manifestURLHeader").classList.add("hidden");
document.querySelector("#locationHeader").classList.add("hidden");
document.body.className = "";
document.querySelector("#icon").src = "";
document.querySelector("h1").textContent = "";
document.querySelector("#description").textContent = "";
document.querySelector("#type").textContent = "";
document.querySelector("#manifestURL").textContent = "";
document.querySelector("#location").textContent = "";
document.querySelector("#errorslist").innerHTML = "";
document.querySelector("#warningslist").innerHTML = "";
}
function updateUI() {
resetUI();
let project = AppManager.selectedProject;
if (!project) {
return;
}
if (project.type != "runtimeApp") {
document.querySelector("#toolbar").classList.remove("hidden");
document.querySelector("#locationHeader").classList.remove("hidden");
document.querySelector("#location").textContent = project.location;
}
document.body.className = project.validationStatus;
document.querySelector("#icon").src = project.icon;
document.querySelector("h1").textContent = project.name;
let manifest;
if (project.type == "runtimeApp") {
manifest = project.app.manifest;
} else {
manifest = project.manifest;
}
if (manifest) {
if (manifest.description) {
document.querySelector("#descriptionHeader").classList.remove("hidden");
document.querySelector("#description").textContent = manifest.description;
}
document.querySelector("#type").classList.remove("hidden");
if (project.type == "runtimeApp") {
document.querySelector("#type").textContent = manifest.type || "web";
} else {
document.querySelector("#type").textContent = project.type + " " + (manifest.type || "web");
}
if (project.type == "packaged") {
let manifest = AppManager.getProjectManifestURL(project);
if (manifest) {
document.querySelector("#manifestURLHeader").classList.remove("hidden");
document.querySelector("#manifestURL").textContent = manifest;
}
}
}
let errorsNode = document.querySelector("#errorslist");
let warningsNode = document.querySelector("#warningslist");
if (project.errors) {
for (let e of project.errors) {
let li = document.createElement("li");
li.textContent = e;
errorsNode.appendChild(li);
}
}
if (project.warnings) {
for (let w of project.warnings) {
let li = document.createElement("li");
li.textContent = w;
warningsNode.appendChild(li);
}
}
}
function removeProject() {
AppManager.removeSelectedProject();
}

View File

@ -0,0 +1,52 @@
<?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 [
<!ENTITY % webideDTD SYSTEM "chrome://webide/locale/webide.dtd" >
%webideDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf8"/>
<link rel="stylesheet" href="chrome://webide/skin/details.css" type="text/css"/>
<script type="application/javascript;version=1.8" src="chrome://webide/content/details.js"></script>
</head>
<body>
<div id="toolbar">
<button onclick="removeProject()">&details_removeProject_button;</button>
<p id="validation_status">
<span class="valid">&details_valid_header;</span>
<span class="warning">&details_warning_header;</span>
<span class="error">&details_error_header;</span>
</p>
</div>
<header>
<img id="icon"></img>
<div>
<h1></h1>
<p id="type"></p>
</div>
</header>
<main>
<h3 id="descriptionHeader">&details_description;</h3>
<p id="description"></p>
<h3 id="locationHeader">&details_location;</h3>
<p id="location"></p>
<h3 id="manifestURLHeader">&details_manifestURL;</h3>
<p id="manifestURL"></p>
</main>
<ul class="validation_messages" id="errorslist"></ul>
<ul class="validation_messages" id="warningslist"></ul>
</body>
</html>

View File

@ -0,0 +1,12 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
webide.jar:
% content webide %content/
content/webide.xul (webide.xul)
content/webide.js (webide.js)
content/newapp.xul (newapp.xul)
content/newapp.js (newapp.js)
content/details.xhtml (details.xhtml)
content/details.js (details.js)

View File

@ -0,0 +1,7 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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']

View File

@ -0,0 +1,161 @@
/* 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/. */
const Cc = Components.classes;
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ZipUtils", "resource://gre/modules/ZipUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
const {AppProjects} = require("devtools/app-manager/app-projects");
const APP_CREATOR_LIST = "devtools.webide.templatesURL";
const {AppManager} = require("devtools/app-manager");
let gTemplateList = null;
window.addEventListener("load", function onLoad() {
window.removeEventListener("load", onLoad);
let projectNameNode = document.querySelector("#project-name");
projectNameNode.addEventListener("input", canValidate, true);
getJSON();
}, true);
function getJSON() {
let xhr = new XMLHttpRequest();
xhr.overrideMimeType('text/plain');
xhr.onload = function() {
let list;
try {
list = JSON.parse(this.responseText);
if (!Array.isArray(list)) {
throw new Error("JSON response not an array");
}
if (list.length == 0) {
throw new Error("JSON response is an empty array");
}
} catch(e) {
return failAndBail("Invalid response from server");
}
gTemplateList = list;
let templatelistNode = document.querySelector("#templatelist");
templatelistNode.innerHTML = "";
for (let template of list) {
let richlistitemNode = document.createElement("richlistitem");
let imageNode = document.createElement("image");
imageNode.setAttribute("src", template.icon);
let labelNode = document.createElement("label");
labelNode.setAttribute("value", template.name);
let descriptionNode = document.createElement("description");
descriptionNode.textContent = template.description;
let vboxNode = document.createElement("vbox");
vboxNode.setAttribute("flex", "1");
richlistitemNode.appendChild(imageNode);
vboxNode.appendChild(labelNode);
vboxNode.appendChild(descriptionNode);
richlistitemNode.appendChild(vboxNode);
templatelistNode.appendChild(richlistitemNode);
}
templatelistNode.selectedIndex = 0;
};
xhr.onerror = function() {
failAndBail("Can't download app templates");
};
let url = Services.prefs.getCharPref(APP_CREATOR_LIST);
xhr.open("get", url);
xhr.send();
}
function failAndBail(msg) {
let promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
promptService.alert(window, "error", msg);
window.close();
}
function canValidate() {
let projectNameNode = document.querySelector("#project-name");
let dialogNode = document.querySelector("dialog");
if (projectNameNode.value.length > 0) {
dialogNode.removeAttribute("buttondisabledaccept");
} else {
dialogNode.setAttribute("buttondisabledaccept", "true");
}
}
function doOK() {
let projectName = document.querySelector("#project-name").value;
if (!projectName) {
AppManager.console.error("No project name");
return false;
}
if (!gTemplateList) {
AppManager.console.error("No template index");
return false;
}
let templatelistNode = document.querySelector("#templatelist");
if (templatelistNode.selectedIndex < 0) {
AppManager.console.error("No template selected");
return false;
}
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, "Select directory where to create app directory", Ci.nsIFilePicker.modeGetFolder);
let res = fp.show();
if (res == Ci.nsIFilePicker.returnCancel) {
AppManager.console.error("No directory selected");
return false;
}
let folder = fp.file;
// Create subfolder with fs-friendly name of project
let subfolder = projectName.replace(/\W/g, '').toLowerCase();
folder.append(subfolder);
try {
folder.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
} catch(e) {
AppManager.console.error(e);
return false;
}
// Download boilerplate zip
let template = gTemplateList[templatelistNode.selectedIndex];
let source = template.file;
let target = folder.clone();
target.append(subfolder + ".zip");
let bail = (e) => {
AppManager.console.error(e);
window.close();
};
Downloads.fetch(source, target).then(() => {
ZipUtils.extractFiles(target, folder);
target.remove(false);
AppProjects.addPackaged(folder).then((project) => {
window.arguments[0].location = project.location;
AppManager.validateProject(project).then(() => {
if (project.manifest) {
project.manifest.name = projectName;
AppManager.writeManifest(project).then(() => {
AppManager.validateProject(project).then(
() => {window.close()}, bail)
}, bail)
} else {
bail("Manifest not found");
}
}, bail)
}, bail)
}, bail);
return false;
}

View File

@ -0,0 +1,33 @@
<?xml version="1.0"?>
<!-- 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 window [
<!ENTITY % webideDTD SYSTEM "chrome://webide/locale/webide.dtd" >
%webideDTD;
]>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://webide/skin/newapp.css"?>
<dialog id="webide:newapp" title="&newAppWindowTitle;"
width="600" height="400"
buttons="accept,cancel"
ondialogaccept="return doOK();"
buttondisabledaccept="true"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="newapp.js"></script>
<label class="header-name" value="&newAppHeader;"/>
<richlistbox id="templatelist" flex="1">
<description>&newAppLoadingTemplate;</description>
</richlistbox>
<vbox>
<label class="header-name" control="project-name" value="&newAppProjectName;"/>
<textbox id="project-name"/>
</vbox>
</dialog>

View File

@ -0,0 +1,735 @@
/* 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/. */
const Cc = Components.classes;
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource:///modules/devtools/gDevTools.jsm");
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const {require} = devtools;
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const {AppProjects} = require("devtools/app-manager/app-projects");
const {Connection} = require("devtools/client/connection-manager");
const {AppManager} = require("devtools/app-manager");
const Strings = Services.strings.createBundle("chrome://webide/locale/webide.properties");
const HTML = "http://www.w3.org/1999/xhtml";
window.addEventListener("load", function onLoad() {
window.removeEventListener("load", onLoad);
UI.init();
});
window.addEventListener("unload", function onUnload() {
window.removeEventListener("unload", onUnload);
UI.uninit();
});
let UI = {
init: function() {
AppManager.init();
this.onMessage = this.onMessage.bind(this);
window.addEventListener("message", this.onMessage);
this.appManagerUpdate = this.appManagerUpdate.bind(this);
AppManager.on("app-manager-update", this.appManagerUpdate);
this.logNode = document.querySelector("#logs");
this.updateCommands();
this.updateRuntimeList();
this.onfocus = this.onfocus.bind(this);
window.addEventListener("focus", this.onfocus, true);
try {
let lastProjectLocation = Services.prefs.getCharPref("devtools.webide.lastprojectlocation");
AppProjects.load().then(() => {
let lastProject = AppProjects.get(lastProjectLocation);
if (lastProject) {
AppManager.selectedProject = lastProject;
} else {
AppManager.selectedProject = null;
}
});
} catch(e) {
AppManager.selectedProject = null;
}
document.querySelector("#toggle-logs").addEventListener("click", function() {
document.querySelector("#logs").classList.toggle("expand");
UI.logNode.scrollTop = UI.logNode.scrollTopMax;
});
},
uninit: function() {
window.removeEventListener("focus", this.onfocus, true);
AppManager.off("app-manager-update", this.appManagerUpdate);
AppManager.uninit();
window.removeEventListener("message", this.onMessage);
},
onfocus: function() {
// Because we can't track the activity in the folder project,
// we need to validate the project regularly. Let's assume that
// if a modification happened, it happened when the window was
// not focused.
if (AppManager.selectedProject &&
AppManager.selectedProject.type != "runtimeApp") {
AppManager.validateProject(AppManager.selectedProject);
}
},
appManagerUpdate: function(event, what, details) {
// Got a message from app-manager.js
switch (what) {
case "console":
if (details.level == "log") this.console.log(details.message);
if (details.level == "warning") this.console.warning(details.message);
if (details.level == "error") this.console.error(details.message);
if (details.level == "success") this.console.success(details.message);
break;
case "runtimelist":
this.updateRuntimeList();
break;
case "connection":
this.updateRuntimeButton();
this.updateCommands();
break;
case "project":
this.updateTitle();
this.closeToolbox();
this.updateCommands();
this.updateProjectButton();
this.openProject();
break;
case "project-is-not-running":
case "project-is-running":
this.updateCommands();
break;
case "runtime":
this.updateRuntimeButton();
break;
case "project-validated":
this.updateTitle();
this.updateCommands();
this.updateProjectButton();
break;
};
},
openInBrowser: function(url) {
// Open a URL in a Firefox window
let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
if (browserWin) {
let gBrowser = browserWin.gBrowser;
gBrowser.selectedTab = gBrowser.addTab(url);
browserWin.focus();
} else {
window.open(url);
}
},
updateTitle: function() {
let project = AppManager.selectedProject;
if (project) {
window.document.title = Strings.formatStringFromName("title_app", [project.name], 1);
} else {
window.document.title = Strings.GetStringFromName("title_noApp");
}
},
hidePanels: function() {
let panels = document.querySelectorAll("panel");
for (let p of panels) {
p.hidePopup();
}
},
busy: function() {
document.querySelector("window").classList.add("busy")
this.updateCommands();
},
unbusy: function() {
document.querySelector("window").classList.remove("busy")
this.updateCommands();
},
busyUntil: function(promise, operationDescription) {
// Freeze the UI until the promise is resolved. A 30s timeout
// will unfreeze the UI, just in case the promise never gets
// resolved.
let timeout = setTimeout(() => {
this.unbusy();
this.console.error("Operation timeout: " + operationDescription);
}, 30000);
this.busy();
promise.then(() => {
clearTimeout(timeout);
this.unbusy();
}, () => {
clearTimeout(timeout);
this.unbusy();
});
},
/********** RUNTIME **********/
updateRuntimeList: function() {
let USBListNode = document.querySelector("#runtime-panel-usbruntime");
let simulatorListNode = document.querySelector("#runtime-panel-simulators");
while (USBListNode.hasChildNodes()) {
USBListNode.firstChild.remove();
}
this.console.log("Found " + AppManager.runtimeList.usb.length + " USB devices.");
this.console.log("Found " + AppManager.runtimeList.simulators.length + " simulators.");
for (let runtime of AppManager.runtimeList.usb) {
let panelItemNode = document.createElement("toolbarbutton");
panelItemNode.className = "panel-item runtime-panel-item-usbruntime";
panelItemNode.setAttribute("label", runtime.getName());
USBListNode.appendChild(panelItemNode);
let r = runtime;
panelItemNode.addEventListener("click", () => {
this.hidePanels();
this.connectToRuntime(r);
}, true);
}
while (simulatorListNode.hasChildNodes()) {
simulatorListNode.firstChild.remove();
}
for (let runtime of AppManager.runtimeList.simulators) {
let panelItemNode = document.createElement("toolbarbutton");
panelItemNode.className = "panel-item runtime-panel-item-simulator";
panelItemNode.setAttribute("label", runtime.getName());
simulatorListNode.appendChild(panelItemNode);
let r = runtime;
panelItemNode.addEventListener("click", () => {
this.hidePanels();
this.connectToRuntime(r);
}, true);
}
},
connectToRuntime: function(runtime) {
let name = runtime.getName();
let promise = AppManager.connectToRuntime(runtime);
this.busyUntil(promise, "connecting to runtime");
promise.then(
() => {this.console.success("Connected to " + name)},
() => {this.console.error("Can't connect to " + name)});
},
updateRuntimeButton: function() {
let buttonNode = document.querySelector("#runtime-panel-button");
let labelNode = buttonNode.querySelector(".panel-button-label");
if (!AppManager.selectedRuntime) {
labelNode.setAttribute("value", Strings.GetStringFromName("runtimeButton_label"));
} else {
let name = AppManager.selectedRuntime.getName();
labelNode.setAttribute("value", name);
}
},
/********** PROJECTS **********/
// Panel & button
updateProjectButton: function() {
let buttonNode = document.querySelector("#project-panel-button");
let labelNode = buttonNode.querySelector(".panel-button-label");
let imageNode = buttonNode.querySelector(".panel-button-image");
let project = AppManager.selectedProject;
if (!project) {
buttonNode.classList.add("no-project");
labelNode.setAttribute("value", Strings.GetStringFromName("projectButton_label"));
imageNode.removeAttribute("src");
} else {
buttonNode.classList.remove("no-project");
labelNode.setAttribute("value", project.name);
imageNode.setAttribute("src", project.icon);
}
},
// details.xhtml
openProject: function() {
let details = document.querySelector("#details");
let project = AppManager.selectedProject;
if (!project) {
details.setAttribute("hidden", "true");
return;
}
if (project.location) {
Services.prefs.setCharPref("devtools.webide.lastprojectlocation", project.location);
}
details.removeAttribute("hidden");
},
/********** COMMANDS **********/
updateCommands: function() {
if (document.querySelector("window").classList.contains("busy")) {
document.querySelector("#cmd_newApp").setAttribute("disabled", "true");
document.querySelector("#cmd_importPackagedApp").setAttribute("disabled", "true");
document.querySelector("#cmd_importHostedApp").setAttribute("disabled", "true");
document.querySelector("#cmd_showProjectPanel").setAttribute("disabled", "true");
document.querySelector("#cmd_showRuntimePanel").setAttribute("disabled", "true");
document.querySelector("#cmd_removeProject").setAttribute("disabled", "true");
document.querySelector("#cmd_disconnectRuntime").setAttribute("disabled", "true");
document.querySelector("#cmd_showPermissionsTable").setAttribute("disabled", "true");
document.querySelector("#cmd_takeScreenshot").setAttribute("disabled", "true");
document.querySelector("#cmd_showRuntimeDetails").setAttribute("disabled", "true");
document.querySelector("#cmd_play").setAttribute("disabled", "true");
document.querySelector("#cmd_stop").setAttribute("disabled", "true");
document.querySelector("#cmd_toggleToolbox").setAttribute("disabled", "true");
return;
}
document.querySelector("#cmd_newApp").removeAttribute("disabled");
document.querySelector("#cmd_importPackagedApp").removeAttribute("disabled");
document.querySelector("#cmd_importHostedApp").removeAttribute("disabled");
document.querySelector("#cmd_showProjectPanel").removeAttribute("disabled");
document.querySelector("#cmd_showRuntimePanel").removeAttribute("disabled");
document.querySelector("#runtime-panel-button").removeAttribute("active");
// Action commands
let playCmd = document.querySelector("#cmd_play");
let stopCmd = document.querySelector("#cmd_stop");
let debugCmd = document.querySelector("#cmd_toggleToolbox");
if (!AppManager.selectedProject || AppManager.connection.status != Connection.Status.CONNECTED) {
playCmd.setAttribute("disabled", "true");
stopCmd.setAttribute("disabled", "true");
debugCmd.setAttribute("disabled", "true");
} else {
let isProjectRunning = AppManager.isProjectRunning();
if (isProjectRunning) {
stopCmd.removeAttribute("disabled");
debugCmd.removeAttribute("disabled");
} else {
stopCmd.setAttribute("disabled", "true");
debugCmd.setAttribute("disabled", "true");
}
// If connected and a project is selected
if (AppManager.selectedProject.type == "runtimeApp") {
if (isProjectRunning) {
playCmd.setAttribute("disabled", "true");
} else {
playCmd.removeAttribute("disabled");
}
} else {
if (AppManager.selectedProject.errorsCount == 0) {
playCmd.removeAttribute("disabled");
} else {
playCmd.setAttribute("disabled", "true");
}
}
document.querySelector("#runtime-panel-button").setAttribute("active", "true");
}
// Remove command
let removeCmdNode = document.querySelector("#cmd_removeProject");
if (AppManager.selectedProject) {
removeCmdNode.removeAttribute("disabled");
} else {
removeCmdNode.setAttribute("disabled", "true");
}
// Runtime commands
let screenshotCmd = document.querySelector("#cmd_takeScreenshot");
let permissionsCmd = document.querySelector("#cmd_showPermissionsTable");
let detailsCmd = document.querySelector("#cmd_showRuntimeDetails");
let disconnectCmd = document.querySelector("#cmd_disconnectRuntime");
let box = document.querySelector("#runtime-actions");
if (AppManager.connection.status == Connection.Status.CONNECTED) {
screenshotCmd.removeAttribute("disabled");
permissionsCmd.removeAttribute("disabled");
disconnectCmd.removeAttribute("disabled");
detailsCmd.removeAttribute("disabled");
box.removeAttribute("hidden");
} else {
screenshotCmd.setAttribute("disabled", "true");
permissionsCmd.setAttribute("disabled", "true");
disconnectCmd.setAttribute("disabled", "true");
detailsCmd.setAttribute("disabled", "true");
box.setAttribute("hidden", "true");
}
},
/********** TOOLBOX **********/
onMessage: function(event) {
// The custom toolbox sends a message to its parent
// window.
try {
let json = JSON.parse(event.data);
switch (json.name) {
case "toolbox-close":
this.closeToolboxUI();
break;
}
} catch(e) { Cu.reportError(e); }
},
closeToolbox: function() {
if (this.toolboxPromise) {
this.toolboxPromise.then(toolbox => {
toolbox.destroy();
document.querySelector("#action-button-debug").removeAttribute("active");
this.toolboxPromise = null;
}, this.console.error);
}
},
showToolbox: function(target) {
if (this.toolboxIframe) {
return;
}
let splitter = document.querySelector(".devtools-horizontal-splitter");
splitter.removeAttribute("hidden");
let iframe = document.createElement("iframe");
document.querySelector("window").insertBefore(iframe, splitter.nextSibling);
let host = devtools.Toolbox.HostType.CUSTOM;
let options = { customIframe: iframe };
this.toolboxIframe = iframe;
let height = Services.prefs.getIntPref("devtools.toolbox.footer.height");
iframe.height = height;
document.querySelector("#action-button-debug").setAttribute("active", "true");
return gDevTools.showToolbox(target, null, host, options);
},
closeToolboxUI: function() {
let body = document.querySelector("#body");
body.removeAttribute("hidden");
Services.prefs.setIntPref("devtools.toolbox.footer.height", this.toolboxIframe.height);
// We have to destroy the iframe, otherwise, the keybindings of webide don't work
// properly anymore.
this.toolboxIframe.remove();
this.toolboxIframe = null;
let splitter = document.querySelector(".devtools-horizontal-splitter");
splitter.setAttribute("hidden", "true");
},
console: {
_log: function(msg, classname) {
let li = document.createElementNS(HTML, "p");
li.textContent = msg;
li.className = classname;
UI.logNode.appendChild(li);
UI.logNode.scrollTop = UI.logNode.scrollTopMax;
},
log: function(msg) {
UI.console._log(msg, "log");
console.log(msg);
},
warning: function(msg) {
UI.console._log(msg, "warning");
console.warning(msg);
},
error: function(msg) {
UI.console._log(msg, "error");
console.error(msg);
},
success: function(msg) {
UI.console._log(msg, "success");
console.log(msg);
},
},
}
let Cmds = {
quit: function() {
window.close();
},
newApp: function() {
UI.hidePanels();
let ret = {location:null};
window.openDialog("chrome://webide/content/newapp.xul", "newapp", "chrome,modal", ret);
if (!ret.location)
return;
let project = AppProjects.get(ret.location);
UI.busyUntil(AppManager.validateProject(project).then(() => {
UI.console.success("New project created at " + ret.location);
AppManager.selectedProject = project;
}, (e) => UI.console.error("Error while create new app: " + e)), "creating new app");;
},
importPackagedApp: function() {
UI.hidePanels();
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, Strings.GetStringFromName("importPackagedApp_title"), Ci.nsIFilePicker.modeGetFolder);
let res = fp.show();
if (res == Ci.nsIFilePicker.returnCancel)
return;
UI.busyUntil(AppProjects.addPackaged(fp.file)
.then(project => AppManager.validateProject(project))
.then(project => AppManager.selectedProject = project)
.then(( ) => { UI.console.log("New project successfuly added") },
(e) => { UI.console.error("Error while importing project: " + e) }),
"importing packaged app");
},
importHostedApp: function() {
UI.hidePanels();
let promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
let ret = {value:null};
promptService.prompt(window,
Strings.GetStringFromName("importHostedApp_title"),
Strings.GetStringFromName("importHostedApp_header"),
ret, null, {});
let url = ret.value;
if (!url)
return;
UI.busyUntil(AppProjects.addHosted(url)
.then(project => AppManager.validateProject(project))
.then(project => AppManager.selectedProject = project)
.then(( ) => { UI.console.log("New project successfuly added") },
(e) => { UI.console.error("Error while importing project: " + e) }),
"importing hosted app");
},
showProjectPanel: function() {
let panelNode = document.querySelector("#project-panel");
let panelVboxNode = document.querySelector("#project-panel > vbox");
let anchorNode = document.querySelector("#project-panel-button > .panel-button-anchor");
let projectsNode = document.querySelector("#project-panel-projects");
while (projectsNode.hasChildNodes()) {
projectsNode.firstChild.remove();
}
AppProjects.load().then(() => {
let projects = AppProjects.store.object.projects;
for (let i = 0; i < projects.length; i++) {
let project = projects[i];
let panelItemNode = document.createElement("toolbarbutton");
panelItemNode.className = "panel-item";
projectsNode.appendChild(panelItemNode);
panelItemNode.setAttribute("label", project.name || AppManager.DEFAULT_PROJECT_NAME);
panelItemNode.setAttribute("image", project.icon || AppManager.DEFAULT_PROJECT_ICON);
if (!project.validationStatus) {
// The result of the validation process (storing names, icons, …) has never been
// stored in the IndexedDB database. This happens when the project has been created
// from the old app manager. We need to run the validation again and update the name
// and icon of the app
AppManager.validateProject(project).then(() => {
panelItemNode.setAttribute("label", project.name);
panelItemNode.setAttribute("image", project.icon);
});
}
panelItemNode.addEventListener("click", () => {
UI.hidePanels();
AppManager.selectedProject = project;
}, true);
}
window.setTimeout(() => {
// Open the popup only when the projects are added.
// Not doing it in the next tick can cause mis-calculations
// of the size of the panel.
panelNode.openPopup(anchorNode);
panelVboxNode.scrollTop = 0;
}, 0);
}, UI.console.error);
let runtimeappsHeaderNode = document.querySelector("#panel-header-runtimeapps");
if (AppManager.connection.status == Connection.Status.CONNECTED) {
runtimeappsHeaderNode.removeAttribute("hidden");
} else {
runtimeappsHeaderNode.setAttribute("hidden", "true");
}
let runtimeAppsNode = document.querySelector("#project-panel-runtimeapps");
while (runtimeAppsNode.hasChildNodes()) {
runtimeAppsNode.firstChild.remove();
}
UI.console.log("Found " + AppManager.webAppsStore.object.all.length + " apps");
for (let i = 0; i < AppManager.webAppsStore.object.all.length; i++) {
let app = AppManager.webAppsStore.object.all[i];
let panelItemNode = document.createElement("toolbarbutton");
panelItemNode.className = "panel-item";
panelItemNode.setAttribute("label", app.name);
panelItemNode.setAttribute("image", app.iconURL);
runtimeAppsNode.appendChild(panelItemNode);
panelItemNode.addEventListener("click", () => {
UI.hidePanels();
AppManager.selectedProject = {
type: "runtimeApp",
app: app,
icon: app.iconURL,
name: app.name
};
}, true);
}
},
showRuntimePanel: function() {
let panel = document.querySelector("#runtime-panel");
let anchor = document.querySelector("#runtime-panel-button > .panel-button-anchor");
panel.openPopup(anchor);
},
disconnectRuntime: function() {
UI.busyUntil(AppManager.disconnectRuntime());
},
takeScreenshot: function() {
UI.hidePanels();
UI.busyUntil(AppManager.deviceFront.screenshotToDataURL().then(longstr => {
return longstr.string().then(dataURL => {
longstr.release().then(null, UI.console.error);
UI.openInBrowser(dataURL);
});
}));
},
showPermissionsTable: function() {
UI.hidePanels();
UI.busyUntil(AppManager.deviceFront.getRawPermissionsTable().then(json => {
let styleContent = "";
styleContent += "body {background:white; font-family: monospace}";
styleContent += "table {border-collapse: collapse}";
styleContent += "th, td {padding: 5px; border: 1px solid #EEE}";
styleContent += "th {min-width: 130px}";
styleContent += "td {text-align: center}";
styleContent += "th:first-of-type, td:first-of-type {text-align:left}";
styleContent += ".permallow {color:rgb(152, 207, 57)}";
styleContent += ".permprompt {color:rgb(0,158,237)}";
styleContent += ".permdeny {color:rgb(204,73,8)}";
let style = document.createElementNS(HTML, "style");
style.textContent = styleContent;
let table = document.createElementNS(HTML, "table");
table.innerHTML = "<tr><th>Name</th><th>type:web</th><th>type:privileged</th><th>type:certified</th></tr>";
let permissionsTable = json.rawPermissionsTable;
for (let name in permissionsTable) {
let tr = document.createElementNS(HTML, "tr");
let td = document.createElementNS(HTML, "td");
td.textContent = name;
tr.appendChild(td);
for (let type of ["app","privileged","certified"]) {
let td = document.createElementNS(HTML, "td");
if (permissionsTable[name][type] == json.ALLOW_ACTION) {
td.textContent = "✓";
td.className = "permallow";
}
if (permissionsTable[name][type] == json.PROMPT_ACTION) {
td.textContent = "!";
td.className = "permprompt";
}
if (permissionsTable[name][type] == json.DENY_ACTION) {
td.textContent = "✕";
td.className = "permdeny"
}
tr.appendChild(td);
}
table.appendChild(tr);
}
let body = document.createElementNS(HTML, "body");
body.appendChild(style);
body.appendChild(table);
let url = "data:text/html;charset=utf-8,";
url += encodeURIComponent(body.outerHTML);
UI.openInBrowser(url);
}), "showing permission table");
},
showRuntimeDetails: function() {
UI.hidePanels();
UI.busyUntil(AppManager.deviceFront.getDescription().then(json => {
let styleContent = "";
styleContent += "body {background:white; font-family: monospace}";
styleContent += "table {border-collapse: collapse}";
styleContent += "th, td {padding: 5px; border: 1px solid #EEE}";
let style = document.createElementNS(HTML, "style");
style.textContent = styleContent;
let table = document.createElementNS(HTML, "table");
for (let name in json) {
let tr = document.createElementNS(HTML, "tr");
let td = document.createElementNS(HTML, "td");
td.textContent = name;
tr.appendChild(td);
td = document.createElementNS(HTML, "td");
td.textContent = json[name];
tr.appendChild(td);
table.appendChild(tr);
}
let body = document.createElementNS(HTML, "body");
body.appendChild(style);
body.appendChild(table);
let url = "data:text/html;charset=utf-8,";
url += encodeURIComponent(body.outerHTML);
UI.openInBrowser(url);
}), "showing runtime details");
},
play: function() {
switch(AppManager.selectedProject.type) {
case "packaged":
case "hosted":
UI.busyUntil(AppManager.installAndRunProject(), "installing and running app");
break;
case "runtimeApp":
UI.busyUntil(AppManager.runRuntimeApp(), "running app");
break;
}
},
stop: function() {
UI.busyUntil(AppManager.stopRunningApp(), "stopping app");
},
toggleToolbox: function() {
if (UI.toolboxIframe) {
UI.closeToolbox();
} else {
UI.toolboxPromise = AppManager.getTarget().then((target) => {
return UI.showToolbox(target);
}, UI.console.error);
UI.busyUntil(UI.toolboxPromise, "opening toolbox");
}
},
removeProject: function() {
AppManager.removeSelectedProject();
},
toggleEditors: function() {
// Toggle Itchpad
},
}

View File

@ -0,0 +1,164 @@
<?xml version="1.0"?>
<!-- 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 window [
<!ENTITY % webideDTD SYSTEM "chrome://webide/locale/webide.dtd" >
%webideDTD;
]>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://webide/skin/webide.css"?>
<window id="webide"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="&windowTitle;"
windowtype="devtools:webide"
macanimationtype="document"
fullscreenbutton="true"
screenX="4" screenY="4"
width="640" height="480"
persist="screenX screenY width height">
<script type="application/javascript" src="webide.js"></script>
<commandset id="mainCommandSet">
<commandset id="editMenuCommands"/>
<commandset id="webideCommands">
<command id="cmd_quit" oncommand="Cmds.quit()"/>
<command id="cmd_newApp" oncommand="Cmds.newApp()" label="&projectMenu_newApp_label;"/>
<command id="cmd_importPackagedApp" oncommand="Cmds.importPackagedApp()" label="&projectMenu_importPackagedApp_label;"/>
<command id="cmd_importHostedApp" oncommand="Cmds.importHostedApp()" label="&projectMenu_importHostedApp_label;"/>
<command id="cmd_removeProject" oncommand="Cmds.removeProject()" label="&projectMenu_remove_label;"/>
<command id="cmd_showProjectPanel" oncommand="Cmds.showProjectPanel()"/>
<command id="cmd_showRuntimePanel" oncommand="Cmds.showRuntimePanel()"/>
<command id="cmd_disconnectRuntime" oncommand="Cmds.disconnectRuntime()" label="&runtimeMenu_disconnect_label;"/>
<command id="cmd_showPermissionsTable" oncommand="Cmds.showPermissionsTable()" label="&runtimeMenu_showPermissionTable_label;"/>
<command id="cmd_showRuntimeDetails" oncommand="Cmds.showRuntimeDetails()" label="&runtimeMenu_showDetails_label;"/>
<command id="cmd_takeScreenshot" oncommand="Cmds.takeScreenshot()" label="&runtimeMenu_takeScreenshot_label;"/>
<command id="cmd_toggleEditor" oncommand="Cmds.toggleEditors()" label="&viewMenu_toggleEditor_label;"/>
<command id="cmd_play" oncommand="Cmds.play()"/>
<command id="cmd_stop" oncommand="Cmds.stop()"/>
<command id="cmd_toggleToolbox" oncommand="Cmds.toggleToolbox()"/>
</commandset>
</commandset>
<menubar id="main-menubar">
<menu id="menu-project" label="&projectMenu_label;" accesskey="&projectMenu_accesskey;">
<menupopup id="menu-project-popup">
<menuitem command="cmd_newApp" accesskey="&projectMenu_newApp_accesskey;"/>
<menuitem command="cmd_importPackagedApp" accesskey="&projectMenu_importPackagedApp_accesskey;"/>
<menuitem command="cmd_importHostedApp" accesskey="&projectMenu_importHostedApp_accesskey;"/>
<menuitem command="cmd_showProjectPanel" key="key_showProjectPanel" label="&projectMenu_selectApp_label;" accesskey="&projectMenu_selectApp_accessley;"/>
<menuseparator/>
<menuitem command="cmd_play" key="key_play" label="&projectMenu_play_label;" accesskey="&projectMenu_play_accesskey;"/>
<menuitem command="cmd_stop" key="key_stop" label="&projectMenu_stop_label;" accesskey="&projectMenu_stop_accesskey;"/>
<menuitem command="cmd_toggleToolbox" key="key_toggleToolbox" label="&projectMenu_debug_label;" accesskey="&projectMenu_debug_accesskey;"/>
<menuseparator/>
<menuitem command="cmd_removeProject" accesskey="&projectMenu_remove_accesskey;"/>
</menupopup>
</menu>
<menu id="menu-runtime" label="&runtimeMenu_label;" accesskey="&runtimeMenu_accesskey;">
<menupopup id="menu-runtime-popup">
<menuitem command="cmd_takeScreenshot" accesskey="&runtimeMenu_takeScreenshot_accesskey;"/>
<menuitem command="cmd_showPermissionsTable" accesskey="&runtimeMenu_showPermissionTable_accesskey;"/>
<menuitem command="cmd_showRuntimeDetails" accesskey="&runtimeMenu_showDetails_accesskey;"/>
<menuseparator/>
<menuitem command="cmd_disconnectRuntime" accesskey="&runtimeMenu_disconnect_accesskey;"/>
</menupopup>
</menu>
<menu id="menu-view" label="&viewMenu_label;" accesskey="&viewMenu_accesskey;">
<menupopup id="menu-ViewPopup">
<menuitem command="cmd_toggleEditor" key="key_toggleEditor" accesskey="&viewMenu_toggleEditor_accesskey;"/>
</menupopup>
</menu>
</menubar>
<keyset id="mainKeyset">
<key key="&key_quit;" id="key_quit" command="cmd_quit" modifiers="accel"/>
<key key="&key_showProjectPanel;" id="key_showProjectPanel" command="cmd_showProjectPanel" modifiers="accel"/>
<key key="&key_play;" id="key_play" command="cmd_play" modifiers="accel"/>
<key key="&key_stop;" id="key_stop" command="cmd_stop" modifiers="accel"/>
<key key="&key_toggleEditor;" id="key_toggleEditor" command="cmd_toggleEditor" modifiers="accel"/>
<key keycode="&key_toggleToolbox;" id="key_toggleToolbox" command="cmd_toggleToolbox"/>
</keyset>
<toolbar id="main-toolbar">
<vbox flex="1">
<hbox id="action-buttons-container" class="busy">
<toolbarbutton id="action-button-play" class="action-button" command="cmd_play" tooltiptext="&projectMenu_play_label;"/>
<toolbarbutton id="action-button-stop" class="action-button" command="cmd_stop" tooltiptext="&projectMenu_stop_label;"/>
<toolbarbutton id="action-button-debug" class="action-button" command="cmd_toggleToolbox" tooltiptext="&projectMenu_debug_label;"/>
<html:img id="action-busy" src="chrome://webide/skin/throbber.svg"/>
</hbox>
<hbox id="panel-buttons-container">
<toolbarbutton id="project-panel-button" class="panel-button no-project" command="cmd_showProjectPanel">
<image class="panel-button-image"/>
<label class="panel-button-label" value="&projectButton_label;"/>
<image class="panel-button-anchor"/>
</toolbarbutton>
<spacer flex="1"/>
<toolbarbutton id="runtime-panel-button" class="panel-button" command="cmd_showRuntimePanel">
<image class="panel-button-image"/>
<label class="panel-button-label" value="&runtimeButton_label;"/>
<image class="panel-button-anchor"/>
</toolbarbutton>
</hbox>
</vbox>
</toolbar>
<popupset>
<!-- App panel -->
<panel id="project-panel" type="arrow" position="bottomcenter topleft" consumeoutsideclicks="true">
<vbox flex="1">
<toolbarbutton class="panel-item project-panel-item-newapp" command="cmd_newApp"/>
<toolbarbutton class="panel-item project-panel-item-openpackaged" command="cmd_importPackagedApp"/>
<toolbarbutton class="panel-item project-panel-item-openhosted" command="cmd_importHostedApp"/>
<label class="panel-header">&projectPanel_myProjects;</label>
<vbox id="project-panel-projects"></vbox>
<label class="panel-header" id="panel-header-runtimeapps" hidden="true">&projectPanel_runtimeApps;</label>
<vbox flex="1" id="project-panel-runtimeapps"/>
</vbox>
</panel>
<!-- Runtime panel -->
<panel id="runtime-panel" type="arrow" position="bottomcenter topright" consumeoutsideclicks="true">
<vbox flex="1">
<label class="panel-header">&runtimePanel_USBDevices;</label>
<vbox id="runtime-panel-usbruntime"></vbox>
<label class="panel-header">&runtimePanel_simulators;</label>
<vbox id="runtime-panel-simulators"></vbox>
<vbox flex="1" id="runtime-actions" hidden="true">
<toolbarbutton class="panel-item" id="runtime-details" command="cmd_showRuntimeDetails"/>
<toolbarbutton class="panel-item" id="runtime-permissions" command="cmd_showPermissionsTable"/>
<toolbarbutton class="panel-item" id="runtime-screenshot" command="cmd_takeScreenshot"/>
</vbox>
</vbox>
</panel>
</popupset>
<vbox flex="1" id="body">
<iframe id="details" flex="1" hidden="true" src="details.xhtml"/>
</vbox>
<splitter hidden="true" class="devtools-horizontal-splitter" orient="vertical"/>
<!-- toolbox iframe will be inserted here -->
<html:div id="logs-container">
<html:pre id="logs"></html:pre>
<html:button id="toggle-logs">&logs;</html:button>
</html:div>
</window>

View 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/.
DEFINES += -DAB_CD=$(AB_CD)

View File

@ -0,0 +1,80 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY windowTitle "Firefox App Manager">
<!ENTITY projectMenu_label "Project">
<!ENTITY projectMenu_accesskey "P">
<!ENTITY projectMenu_newApp_label "New App…">
<!ENTITY projectMenu_newApp_accesskey "N">
<!ENTITY projectMenu_importPackagedApp_label "Open Packaged App…">
<!ENTITY projectMenu_importPackagedApp_accesskey "P">
<!ENTITY projectMenu_importHostedApp_label "Open Hosted App…">
<!ENTITY projectMenu_importHostedApp_accesskey "H">
<!ENTITY projectMenu_selectApp_label "Open App…">
<!ENTITY projectMenu_selectApp_accessley "S">
<!ENTITY projectMenu_play_label "Install and run">
<!ENTITY projectMenu_play_accesskey "I">
<!ENTITY projectMenu_stop_label "Stop App">
<!ENTITY projectMenu_stop_accesskey "S">
<!ENTITY projectMenu_debug_label "Debug App">
<!ENTITY projectMenu_debug_accesskey "D">
<!ENTITY projectMenu_remove_label "Remove Project">
<!ENTITY projectMenu_remove_accesskey "R">
<!ENTITY runtimeMenu_label "Runtime">
<!ENTITY runtimeMenu_accesskey "R">
<!ENTITY runtimeMenu_disconnect_label "Disconnect">
<!ENTITY runtimeMenu_disconnect_accesskey "D">
<!ENTITY runtimeMenu_showPermissionTable_label "Permissions Table">
<!ENTITY runtimeMenu_showPermissionTable_accesskey "P">
<!ENTITY runtimeMenu_takeScreenshot_label "Screenshot">
<!ENTITY runtimeMenu_takeScreenshot_accesskey "S">
<!ENTITY runtimeMenu_showDetails_label "Runtime Info">
<!ENTITY runtimeMenu_showDetails_accesskey "E">
<!ENTITY viewMenu_label "View">
<!ENTITY viewMenu_accesskey "V">
<!ENTITY viewMenu_toggleEditor_label "Toggle Editor">
<!ENTITY viewMenu_toggleEditor_accesskey "E">
<!ENTITY projectButton_label "Open App">
<!ENTITY runtimeButton_label "Select Runtime">
<!-- We try to repicate Firefox' bindings: -->
<!-- quit app -->
<!ENTITY key_quit "Q">
<!-- open menu -->
<!ENTITY key_showProjectPanel "O">
<!-- reload app -->
<!ENTITY key_play "R">
<!-- close app -->
<!ENTITY key_stop "W">
<!-- show toolbox -->
<!ENTITY key_toggleToolbox "VK_F12">
<!-- toggle sidebar -->
<!ENTITY key_toggleEditor "B">
<!ENTITY projectPanel_myProjects "My Projects">
<!ENTITY projectPanel_runtimeApps "Runtime Apps">
<!ENTITY runtimePanel_USBDevices "USB Devices">
<!ENTITY runtimePanel_simulators "Simulators">
<!ENTITY logs "Logs">
<!-- Lense -->
<!ENTITY details_valid_header "valid">
<!ENTITY details_warning_header "warnings">
<!ENTITY details_error_header "errors">
<!ENTITY details_description "Description">
<!ENTITY details_location "Location">
<!ENTITY details_manifestURL "App ID">
<!ENTITY details_removeProject_button "Remove Project">
<!-- New App -->
<!ENTITY newAppWindowTitle "New App">
<!ENTITY newAppHeader "Select template">
<!ENTITY newAppLoadingTemplate "Loading templates…">
<!ENTITY newAppProjectName "Project Name:">

View File

@ -0,0 +1,13 @@
# 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/.
title_noApp=Firefox App Manager
title_app=Firefox App Manager: %S
runtimeButton_label=Select Runtime
projectButton_label=Open App
importPackagedApp_title=Select directory
importHostedApp_title=Open Hosted App
importHostedApp_header=Enter Manifest URL

View File

@ -0,0 +1,10 @@
#filter substitution
# 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/.
webide.jar:
% locale webide @AB_CD@ %locale/
locale/webide.dtd (%webide.dtd)
locale/webide.properties (%webide.properties)

View File

@ -0,0 +1,7 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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']

View File

@ -0,0 +1,540 @@
/* 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/. */
const {Cu} = require("chrome");
let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js");
const {TextEncoder, OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
const {AppProjects} = require("devtools/app-manager/app-projects");
const WebappsStore = require("devtools/app-manager/webapps-store");
const {AppValidator} = require("devtools/app-manager/app-validator");
const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
const AppActorFront = require("devtools/app-actor-front");
const {getDeviceFront} = require("devtools/server/actors/device");
exports.AppManager = AppManager = {
// FIXME: will break when devtools/app-manager will be removed:
DEFAULT_PROJECT_ICON: "chrome://browser/skin/devtools/app-manager/default-app-icon.png",
DEFAULT_PROJECT_NAME: "--",
init: function() {
let host = Services.prefs.getCharPref("devtools.debugger.remote-host");
let port = Services.prefs.getIntPref("devtools.debugger.remote-port");
this.connection = ConnectionManager.createConnection("localhost", port);
this.onConnectionChanged = this.onConnectionChanged.bind(this);
this.connection.on(Connection.Events.STATUS_CHANGED, this.onConnectionChanged);
this.webAppsStore = new WebappsStore(this.connection);
this.runtimeList = {usb:[], simulators:[]};
this.trackUSBRuntimes();
this.trackSimulatorRuntimes();
},
uninit: function() {
this._unlistenToApps();
this.selectedProject = null;
this.selectedRuntime = null;
this.untrackUSBRuntimes();
this.untrackSimulatorRuntimes();
this._runningApps.clear();
this.runtimeList = null;
this.connection.off(Connection.Events.STATUS_CHANGED, this.onConnectionChanged);
this.webAppsStore.destroy();
this._listTabsResponse = null;
this.connection.disconnect();
this.connection = null;
},
console: {
// Forward console.* calls to the UI
log: function(msg) { AppManager.update("console", {level: "log", message: msg}); },
warning: function(msg) { AppManager.update("console", {level: "warning", message: msg}); },
error: function(msg) { AppManager.update("console", {level: "error", message: msg}); },
success: function(msg) { AppManager.update("console", {level: "success", message: msg}); },
},
update: function(what, details) {
// Anything we want to forward to the UI
this.emit("app-manager-update", what, details);
},
onConnectionChanged: function() {
if (this.connection.status == Connection.Status.DISCONNECTED) {
this.selectedRuntime = null;
}
if (this.connection.status != Connection.Status.CONNECTED) {
AppManager.console.log("Connection status changed: " + this.connection.status);
this._runningApps.clear();
this._unlistenToApps();
this._listTabsResponse = null;
} else {
this.connection.client.listTabs((response) => {
this._listenToApps();
this._listTabsResponse = response;
this._getRunningApps();
});
}
this.update("connection");
},
_runningApps: new Set(),
_getRunningApps: function() {
let client = this.connection.client;
let request = {
to: this._listTabsResponse.webappsActor,
type: "listRunningApps"
};
client.request(request, (res) => {
if (res.error) {
AppManager.console.error("listRunningApps error: " + res.error);
}
for (let m of res.apps) {
this._runningApps.add(m);
}
});
this.checkIfProjectIsRunning();
},
_listenToApps: function() {
let client = this.connection.client;
client.addListener("appOpen", (type, { manifestURL }) => {
AppManager.console.log("App open: " + manifestURL);
this._runningApps.add(manifestURL);
this.checkIfProjectIsRunning();
});
client.addListener("appClose", (type, { manifestURL }) => {
AppManager.console.log("App close: " + manifestURL);
this._runningApps.delete(manifestURL);
this.checkIfProjectIsRunning();
});
},
_unlistenToApps: function() {
// Is that even possible?
// connection.client is null now.
},
isProjectRunning: function() {
let manifest = this.getProjectManifestURL(this.selectedProject);
return manifest && this._runningApps.has(manifest);
},
checkIfProjectIsRunning: function() {
if (this.selectedProject) {
if (this.isProjectRunning()) {
AppManager.console.log("Project is running on " + this.selectedRuntime.getName());
this.update("project-is-running");
this._notRunningLogged = false;
} else {
this.update("project-is-not-running");
if (!this._notRunningLogged) {
this._notRunningLogged = true;
AppManager.console.log("Project is not running");
}
}
}
},
getTarget: function() {
let manifest = this.getProjectManifestURL(this.selectedProject);
let name = this.selectedProject.name;
if (manifest) {
let client = this.connection.client;
let actor = this._listTabsResponse.webappsActor;
let promise = AppActorFront.getTargetForApp(client, actor, manifest);
promise.then(( ) => { AppManager.console.log("Connected to app: " + name) },
(e) => { AppManager.console.error("Can't connect to app: " + e) });
return promise;
}
AppManager.console.error("Can't find manifestURL for selected project");
return promise.reject();
},
getProjectManifestURL: function(project) {
let manifest = null;
if (project.type == "runtimeApp") {
manifest = project.app.manifestURL;
}
if (project.type == "hosted") {
manifest = project.location;
}
if (project.type == "packaged" && project.packagedAppOrigin) {
manifest = "app://" + project.packagedAppOrigin + "/manifest.webapp";
}
return manifest;
},
_selectedProject: null,
set selectedProject(value) {
if (value != this.selectedProject) {
this._selectedProject = value;
if (this.selectedProject) {
AppManager.console.log("New project selected: " + this.selectedProject.name);
if (this.selectedProject.type == "runtimeApp") {
this.runRuntimeApp();
} else {
this.validateProject(this.selectedProject);
}
} else {
AppManager.console.log("No project selected");
}
this.update("project");
this.checkIfProjectIsRunning();
}
},
get selectedProject() {
return this._selectedProject;
},
removeSelectedProject: function() {
let location = this.selectedProject.location;
AppManager.selectedProject = null;
AppProjects.remove(location);
},
_selectedRuntime: null,
set selectedRuntime(value) {
this._selectedRuntime = value;
if (!value &&
this.selectedProject &&
this.selectedProject.type == "runtimeApp") {
this.selectedProject = null;
}
this.update("runtime");
},
get selectedRuntime() {
return this._selectedRuntime;
},
connectToRuntime: function(runtime) {
if (this.connection.status == Connection.Status.CONNECTED) {
return promise.reject("Already connected");
}
this.selectedRuntime = runtime;
let deferred = promise.defer();
AppManager.console.log("Connecting to " + runtime.getName());
let onConnectedOrDisconnected = () => {
this.connection.off(Connection.Events.CONNECTED, onConnectedOrDisconnected);
this.connection.off(Connection.Events.DISCONNECTED, onConnectedOrDisconnected);
if (this.connection.status == Connection.Status.CONNECTED) {
deferred.resolve();
} else {
deferred.reject();
}
}
this.connection.on(Connection.Events.CONNECTED, onConnectedOrDisconnected);
this.connection.on(Connection.Events.DISCONNECTED, onConnectedOrDisconnected);
this.selectedRuntime.connect(this.connection).then(
() => {},
() => {deferred.reject()});
return deferred.promise;
},
get deviceFront() {
if (!this._listTabsResponse) {
return null;
}
return getDeviceFront(this.connection.client, this._listTabsResponse);
},
disconnectRuntime: function() {
if (this.connection.status != Connection.Status.CONNECTED) {
return promise.reject("Already disconnected");
}
let deferred = promise.defer();
this.connection.once(Connection.Events.DISCONNECTED, () => deferred.resolve());
this.connection.disconnect();
return deferred.promise;
},
runRuntimeApp: function() {
if (this.selectedProject && this.selectedProject.type != "runtimeApp") {
return promise.reject("attempting to run a non-runtime app");
}
let client = this.connection.client;
let actor = this._listTabsResponse.webappsActor;
let manifest = this.getProjectManifestURL(this.selectedProject);
return AppActorFront.launchApp(client, actor, manifest);
},
installAndRunProject: function() {
let project = this.selectedProject;
if (!project ||
!this._listTabsResponse ||
(project.type != "packaged" && project.type != "hosted")) {
AppManager.console.error("Can't install project. Unknown type of project.");
return promise.reject("Can't install");
}
return this.validateProject(project).then(() => {
if (project.errorsCount > 0) {
AppManager.console.error("Can't install project. Validation errors.");
return;
}
let client = this.connection.client;
let actor = this._listTabsResponse.webappsActor;
let installPromise;
if (project.type == "packaged") {
installPromise = AppActorFront.installPackaged(client, actor, project.location, project.packagedAppOrigin)
.then(({ appId }) => {
// If the packaged app specified a custom origin override,
// we need to update the local project origin
project.packagedAppOrigin = appId;
// And ensure the indexed db on disk is also updated
AppProjects.update(project);
});
}
if (project.type == "hosted") {
let manifestURLObject = Services.io.newURI(project.location, null, null);
let origin = Services.io.newURI(manifestURLObject.prePath, null, null);
let appId = origin.host;
let metadata = {
origin: origin.spec,
manifestURL: project.location
};
installPromise = AppActorFront.installHosted(client, actor, appId, metadata, project.manifest);
}
if (!installPromise) {
return promise.reject("Can't install");
}
return installPromise.then(() => {
let manifest = this.getProjectManifestURL(project);
if (!this._runningApps.has(manifest)) {
AppManager.console.log("Launching app: " + project.name);
AppActorFront.launchApp(client, actor, manifest);
} else {
AppManager.console.log("Reloading app: " + project.name);
AppActorFront.reloadApp(client, actor, manifest);
}
});
}, AppManager.console.error);
},
stopRunningApp: function() {
let client = this.connection.client;
let actor = this._listTabsResponse.webappsActor;
let manifest = this.getProjectManifestURL(this.selectedProject);
return AppActorFront.closeApp(client, actor, manifest);
},
/* PROJECT VALIDATION */
validateProject: function(project) {
if (!project) {
return promise.reject();
}
let validation = new AppValidator(project);
return validation.validate()
.then(() => {
if (validation.manifest) {
let manifest = validation.manifest;
let iconPath;
if (manifest.icons) {
let size = Object.keys(manifest.icons).sort(function(a, b) b - a)[0];
if (size) {
iconPath = manifest.icons[size];
}
}
if (!iconPath) {
project.icon = AppManager.DEFAULT_PROJECT_ICON;
} else {
if (project.type == "hosted") {
let manifestURL = Services.io.newURI(project.location, null, null);
let origin = Services.io.newURI(manifestURL.prePath, null, null);
project.icon = Services.io.newURI(iconPath, null, origin).spec;
} else if (project.type == "packaged") {
let projectFolder = FileUtils.File(project.location);
let folderURI = Services.io.newFileURI(projectFolder).spec;
project.icon = folderURI + iconPath.replace(/^\/|\\/, "");
}
}
project.manifest = validation.manifest;
if ("name" in project.manifest) {
project.name = project.manifest.name;
} else {
project.name = AppManager.DEFAULT_PROJECT_NAME;
}
} else {
project.manifest = null;
project.icon = AppManager.DEFAULT_PROJECT_ICON;
project.name = AppManager.DEFAULT_PROJECT_NAME;
}
project.validationStatus = "valid";
if (validation.warnings.length > 0) {
project.warningsCount = validation.warnings.length;
project.warnings = validation.warnings;
project.validationStatus = "warning";
AppManager.console.warning("Validation (" + project.name + "): found " + validation.warnings.length + " warnings.");
} else {
project.warnings = "";
project.warningsCount = 0;
AppManager.console.log("Validation (" + project.name + "): no warnings found.");
}
if (validation.errors.length > 0) {
project.errorsCount = validation.errors.length;
project.errors = validation.errors;
project.validationStatus = "error";
AppManager.console.error("Validation (" + project.name + "): found " + validation.errors.length + " errors.");
} else {
project.errors = "";
project.errorsCount = 0;
AppManager.console.log("Validation (" + project.name + "): no errors found.");
}
if (project.warningsCount && project.errorsCount) {
project.validationStatus = "error warning";
}
if (this.selectedProject === project) {
this.update("project-validated");
}
if (AppProjects.get(project.location)) {
AppProjects.update(project);
}
return project;
}, AppManager.console.error);
},
/* RUNTIME LIST */
trackUSBRuntimes: function() {
this._updateUSBRuntimes = this._updateUSBRuntimes.bind(this);
Devices.on("register", this._updateUSBRuntimes);
Devices.on("unregister", this._updateUSBRuntimes);
Devices.on("addon-status-updated", this._updateUSBRuntimes);
this._updateUSBRuntimes();
},
untrackUSBRuntimes: function() {
Devices.off("register", this._updateUSBRuntimes);
Devices.off("unregister", this._updateUSBRuntimes);
Devices.off("addon-status-updated", this._updateUSBRuntimes);
},
_updateUSBRuntimes: function() {
this.runtimeList.usb = [];
for (let id of Devices.available()) {
this.runtimeList.usb.push(new USBRuntime(id));
}
this.update("runtimelist");
},
trackSimulatorRuntimes: function() {
this._updateSimulatorRuntimes = this._updateSimulatorRuntimes.bind(this);
Simulator.on("register", this._updateSimulatorRuntimes);
Simulator.on("unregister", this._updateSimulatorRuntimes);
this._updateSimulatorRuntimes();
},
untrackSimulatorRuntimes: function() {
Simulator.off("register", this._updateSimulatorRuntimes);
Simulator.off("unregister", this._updateSimulatorRuntimes);
},
_updateSimulatorRuntimes: function() {
this.runtimeList.simulators = [];
for (let version of Simulator.availableVersions()) {
this.runtimeList.simulators.push(new SimulatorRuntime(version));
}
this.update("runtimelist");
},
writeManifest: function(project) {
if (project.type != "packaged") {
return promise.reject("Not a packaged app");
}
if (!project.manifest) {
project.manifest = {};
}
let folder = project.location;
let manifestPath = OS.Path.join(folder, "manifest.webapp");
let text = JSON.stringify(project.manifest, null, 2);
let encoder = new TextEncoder();
let array = encoder.encode(text);
return OS.File.writeAtomic(manifestPath, array, {tmpPath: manifestPath + ".tmp"});
},
}
EventEmitter.decorate(AppManager);
/* RUNTIMES */
function USBRuntime(id) {
this.id = id;
}
USBRuntime.prototype = {
connect: function(connection) {
let device = Devices.getByName(this.id);
if (!device) {
AppManager.console.error("Can't find device: " + id);
return promise.reject();
}
return device.connect().then((port) => {
connection.host = "localhost";
connection.port = port;
connection.connect();
});
},
getName: function() {
return this.id;
},
}
function SimulatorRuntime(version) {
this.version = version;
}
SimulatorRuntime.prototype = {
connect: function(connection) {
let port = ConnectionManager.getFreeTCPPort();
let simulator = Simulator.getByVersion(this.version);
if (!simulator || !simulator.launch) {
AppManager.console.error("Can't find simulator: " + this.version);
return promise.reject();
}
return simulator.launch({port: port}).then(() => {
connection.port = port;
connection.keepConnecting = true;
connection.connect();
});
},
getName: function() {
return this.version;
},
}

View File

@ -0,0 +1,12 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
PARALLEL_DIRS += [
'content',
'locales',
'themes',
]

View File

@ -0,0 +1,135 @@
/* 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/. */
body {
margin: 0;
background-color: white;
font-family: Lucida Grande, Helvetica, Helvetica Neue, sans-serif;
font-size: 12px;
}
.hidden {
display: none;
}
h1, h3, p {
margin: 0;
}
#toolbar {
background-color: #D8D8D8;
border-bottom: 1px solid #AAA;
}
#toolbar > button {
-moz-appearance: none;
background-color: transparent;
border-width: 0 1px 0 0;
border-color: #AAA;
border-style: solid;
margin: 0;
padding: 0 12px;
font-weight: bold;
height: 24px;
}
#toolbar > button:hover {
background-color: #CCC;
cursor: pointer;
}
#validation_status {
float: right;
text-transform: uppercase;
font-size: 10px;
line-height: 24px;
padding: 0 12px;
color: white;
}
header {
padding: 20px 0;
}
header > div {
vertical-align: top;
display: inline-block;
}
#icon {
height: 48px;
width: 48px;
text-align:top;
float: left;
margin: 0 20px;
}
h1, #type {
line-height: 24px;
height: 24px; /* avoid collapsing if empty */
display: block;
}
h1 {
font-size: 20px;
}
#type {
font-size: 10px;
text-transform: uppercase;
color: #777;
}
main {
padding-left: 88px;
}
h3 {
color: #999;
font-size: 10px;
font-weight: normal;
}
main > p {
margin-bottom: 20px;
}
.validation_messages {
margin-left: 74px;
list-style: none;
border-left: 4px solid transparent;
padding: 0 10px;;
}
body.valid #validation_status {
background-color: #81D135;
}
body.warning #validation_status {
background-color: #FFAC00;
}
body.error #validation_status {
background-color: #ED4C62;
}
#warningslist {
border-color: #FFAC00
}
#errorslist {
border-color: #ED4C62;
}
#validation_status > span {
display: none;
}
body.valid #validation_status > .valid,
body.warning #validation_status > .warning,
body.error #validation_status > .error {
display: inline;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,11 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
webide.jar:
% skin webide classic/1.0 %skin/
* skin/webide.css (webide.css)
skin/icons.png (icons.png)
skin/details.css (details.css)
skin/newapp.css (newapp.css)
skin/throbber.svg (throbber.svg)

View File

@ -0,0 +1,7 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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']

View File

@ -0,0 +1,58 @@
/* 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/. */
dialog {
-moz-appearance: none;
background-image: linear-gradient(rgb(255, 255, 255), rgb(237, 237, 237) 100px);
font-family: "Clear Sans", sans-serif;
color: #424E5A;
overflow-y: scroll;
}
.header-name {
font-size: 1.5rem;
font-weight: normal;
margin: 15px 0;
}
richlistbox {
-moz-appearance: none;
overflow-y: auto;
border: 1px solid #424E5A;
}
richlistitem {
padding: 6px 0;
}
richlistitem:not([selected="true"]):hover {
background-color: rgba(0,0,0,0.04);
}
richlistitem > vbox > label {
margin: 0;
font-size: 1.1em;
}
richlistbox > description {
margin: 8px;
}
richlistitem {
-moz-box-align: start;
}
richlistitem:nth-child(odd) {
background-color:
}
richlistitem > image {
height: 24px;
width: 24px;
margin: 0 6px;
}
textbox {
font-size: 1.2rem;
}

View 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/. -->
<svg xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 64 64">
<g>
<rect x="30" y="4" width="4" height="15" transform="rotate(0, 32, 32)" fill="#BBB"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(30, 32, 32)" fill="#AAA"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(60, 32, 32)" fill="#999"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(90, 32, 32)" fill="#888"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(120, 32, 32)" fill="#777"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(150, 32, 32)" fill="#666"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(180, 32, 32)" fill="#555"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(210, 32, 32)" fill="#444"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(240, 32, 32)" fill="#333"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(270, 32, 32)" fill="#222"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(300, 32, 32)" fill="#111"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(330, 32, 32)" fill="#000"/>
<animateTransform attributeName="transform" type="rotate" calcMode="discrete" values="0 32 32;30 32 32;60 32 32;90 32 32;120 32 32;150 32 32;180 32 32;210 32 32;240 32 32;270 32 32;300 32 32;330 32 32" dur="0.8s" repeatCount="indefinite"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,279 @@
/* 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/. */
#main-toolbar {
padding: 0 12px;
}
#action-buttons-container {
-moz-box-pack: center;
height: 50px;
}
#panel-buttons-container {
height: 50px;
margin-top: -50px;
pointer-events: none;
}
#panel-buttons-container > .panel-button {
pointer-events: auto;
}
#action-busy {
height: 24px;
width: 24px;
}
window.busy .action-button,
window:not(.busy) #action-busy {
display: none;
}
/* Panel buttons */
.panel-button {
-moz-box-align: center;
}
.panel-button-anchor {
list-style-image: url('icons.png');
-moz-image-region: rect(43px, 563px, 61px, 535px);
width: 12;
height: 7px;
margin-bottom: -5px;
}
.panel-button:hover > .panel-button-anchor {
-moz-image-region: rect(243px, 563px, 261px, 535px);
}
/* Panel buttons - projects */
#project-panel-button > .panel-button-image {
width: 18px;
height: 18px;
}
#project-panel-button.no-project > .panel-button-image {
list-style-image: url("icons.png");
-moz-image-region: rect(0px, 740px, 40px, 700px);
}
/* Panel buttons - runtime */
#runtime-panel-button > .panel-button-image {
list-style-image: url('icons.png');
-moz-image-region: rect(25px, 475px, 75px, 425px);
width: 1.2em;
height: 1.2em;
}
#runtime-panel-button[active="true"] > .panel-button-image {
-moz-image-region: rect(125px, 475px, 175px, 425px);
}
/* Action buttons */
.action-button {
-moz-appearance: none;
border-width: 0;
margin: 0;
padding: 0;
list-style-image: url('icons.png');
}
.action-button[disabled="true"] {
opacity: 0.4;
}
.action-button > .toolbarbutton-icon {
width: 40px;
height: 40px;
}
.action-button > .toolbarbutton-text {
display: none;
}
#action-button-play { -moz-image-region: rect(0,100px,100px,0) }
#action-button-stop { -moz-image-region: rect(0,200px,100px,100px) }
#action-button-debug { -moz-image-region: rect(0,400px,100px,300px) }
#action-button-play:not([disabled="true"]):hover { -moz-image-region: rect(200px,100px,300px,0) }
#action-button-stop:not([disabled="true"]):hover { -moz-image-region: rect(200px,200px,300px,100px) }
#action-button-debug:not([disabled="true"]):not([active="true"]):hover { -moz-image-region: rect(200px,400px,300px,300px) }
#action-button-debug[active="true"] { -moz-image-region: rect(100px,400px,200px,300px) }
/* Panels */
panel > vbox {
overflow-x: hidden;
}
panel > .panel-arrowcontainer > .panel-arrowcontent {
padding: 12px 0;
width: 180px;
}
.panel-item {
padding: 3px 12px;
margin: 0;
-moz-appearance: none;
}
.panel-item:hover {
background: #CBF0FE;
}
.panel-header {
/* We can't use borders or vertical padding here because
* panels don't take these into account when calculated the
* height of the panel.
*/
background-color: #EEE;
outline-width: 1px;
outline-color: #D5D5D5;
outline-style: solid;
color: #ACACAC;
text-transform: uppercase;
padding: 0 16px;
line-height: 200%;
margin: 5px 0;
font-size: 90%;
font-weight: bold;
}
.panel-item > .toolbarbutton-icon {
width: 18px;
height: 18px;
}
.panel-item > .toolbarbutton-text {
text-align: start;
}
/* project panel */
.project-panel-item-newapp,
.project-panel-item-openpackaged,
.project-panel-item-openhosted {
list-style-image: url("icons.png");
}
.project-panel-item-newapp { -moz-image-region: rect(0px, 640px, 40px, 600px) }
.project-panel-item-openpackaged { -moz-image-region: rect(0px, 740px, 40px, 700px) }
.project-panel-item-openhosted { -moz-image-region: rect(0px, 840px, 40px, 800px) }
/* runtime panel */
#runtime-panel .panel-arrowcontent {
padding: 12px 0 0;
}
#runtime-panel-simulators {
margin-bottom: 12px;
}
#runtime-permissions,
#runtime-screenshot,
.runtime-panel-item-usbruntime,
.runtime-panel-item-simulator {
list-style-image: url("icons.png");
}
#runtime-screenshot { -moz-image-region: rect(200px, 640px, 240px, 600px) }
#runtime-permissions { -moz-image-region: rect(100px, 840px, 140px, 800px) }
.runtime-panel-item-usbruntime { -moz-image-region: rect(100px, 640px, 140px, 600px) }
.runtime-panel-item-simulator { -moz-image-region: rect(100px, 740px, 140px, 700px) }
#runtime-actions {
border-top: 1px solid rgba(221,221,221,1);
}
#runtime-actions > toolbarbutton {
border-top: 1px solid rgba(221,221,221,1);
background-color: rgba(233,233,233,1);
color: rgba(87,87,87,1);
padding-top: 8px;
padding-bottom: 8px;
}
#runtime-actions > toolbarbutton:hover {
background-color: rgba(221,221,221,1);
}
#runtime-actions > toolbarbutton:last-child {
border-radius: 0 0 3px 3px;
}
/* Main view */
#body {
background-color: rgb(225, 225, 225);
background-image: url('chrome://browser/skin/devtools/app-manager/rocket.svg'), url('chrome://browser/skin/devtools/app-manager/noise.png');
background-repeat: no-repeat, repeat;
background-size: 35%, auto;
background-position: center center, top left;
%ifndef XP_MACOSX
border-top: 1px solid #AAA;
%endif
}
.devtools-horizontal-splitter {
-moz-appearance: none;
background-image: none;
background-color: transparent;
border: 0;
border-bottom: 1px solid rgba(118, 121, 125, .5);
min-height: 3px;
height: 3px;
margin-top: -3px;
position: relative;
border-bottom: 1px solid #aaa;
}
/* Logs */
#logs-container {
position: relative;
}
#logs {
overflow: auto;
padding: 0 6px;
margin: 0;
line-height: 12px;
font-size: 8px;
background-color: #1F1F1F;
color: #8fa1b2;
height: 24px;
}
#logs.expand {
height: 300px;
}
#logs:not(.expand) > .log {
display: none;
}
#toggle-logs {
height: 24px;
border-width: 0;
background-color: #0881C7;
color: white;
margin: 0;
display: block;
position: absolute;
bottom: 0;
right: 0;
}
#logs > p { margin: 0; }
#logs > .warning { color: #d99b28; }
#logs > .error { color: #eb5368; }
#logs > .success { color: #70bf53; }

View File

@ -0,0 +1,6 @@
# -*- Mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
# 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/.
pref("devtools.webide.templatesURL", "http://fixme/");

View File

@ -53,7 +53,7 @@
- for editing commands in text inputs. -->
<!ENTITY btnPageNet.accesskeyMacOSX "t">
<!ENTITY btnPageCSS.label "CSS">
<!ENTITY btnPageCSS.tooltip "Log CSS errors and warnings">
<!ENTITY btnPageCSS.tooltip2 "Log CSS errors and warnings">
<!ENTITY btnPageCSS.accesskey "C">
<!ENTITY btnPageJS.label "JS">
<!ENTITY btnPageJS.tooltip "Log JavaScript exceptions">

View File

@ -27,3 +27,8 @@
<!ENTITY chooseLanguage.label "Choose your preferred language for displaying pages">
<!ENTITY chooseButton.label "Choose…">
<!ENTITY chooseButton.accesskey "o">
<!ENTITY translateWebPages.label "Translate web content">
<!ENTITY translateWebPages.accesskey "T">
<!ENTITY translateExceptions.label "Exceptions…">
<!ENTITY translateExceptions.accesskey "x">

View File

@ -0,0 +1,24 @@
<!-- 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/. -->
<!ENTITY window.title "Exceptions - Translation">
<!ENTITY window.width "36em">
<!ENTITY windowClose.key "w">
<!ENTITY noTranslationForLanguages.label "Translation will not be offered for the following languages:">
<!ENTITY treehead.languageName.label "Languages">
<!ENTITY removeLanguage.label "Remove Language">
<!ENTITY removeLanguage.accesskey "R">
<!ENTITY removeAllLanguages.label "Remove All Languages">
<!ENTITY removeAllLanguages.accesskey "e">
<!ENTITY noTranslationForSites.label "Translation will not be offered for the following sites:">
<!ENTITY treehead.siteName.label "Sites">
<!ENTITY removeSite.label "Remove Site">
<!ENTITY removeSite.accesskey "S">
<!ENTITY removeAllSites.label "Remove All Sites">
<!ENTITY removeAllSites.accesskey "i">
<!ENTITY button.close.label "Close">
<!ENTITY button.close.accesskey "C">

View File

@ -45,3 +45,12 @@
<!ENTITY translation.tryAgain.button "Try Again">
<!ENTITY translation.options.menu "Options">
<!-- LOCALIZATION NOTE (translation.options.neverForSite.accesskey,
- translation.options.preferences.accesskey):
- The accesskey values used here should not clash with the value used for
- translation.options.neverForLanguage.accesskey in translation.properties
-->
<!ENTITY translation.options.neverForSite.label "Never translate this site">
<!ENTITY translation.options.neverForSite.accesskey "e">
<!ENTITY translation.options.preferences.label "Translation preferences">
<!ENTITY translation.options.preferences.accesskey "T">

View File

@ -0,0 +1,12 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# LOCALIZATION NOTE (translation.options.neverForLanguage.label):
# %S is a language name coming from the global/languageNames.properties file.
translation.options.neverForLanguage.label=Never translate %S
# LOCALIZATION NOTE (translation.options.neverForLanguage.accesskey):
# The accesskey value used here should not clash with the values used for
# translation.options.*.accesskey in translation.dtd
translation.options.neverForLanguage.accesskey=N

View File

@ -82,6 +82,7 @@
locale/browser/tabview.properties (%chrome/browser/tabview.properties)
locale/browser/taskbar.properties (%chrome/browser/taskbar.properties)
locale/browser/translation.dtd (%chrome/browser/translation.dtd)
locale/browser/translation.properties (%chrome/browser/translation.properties)
locale/browser/downloads/downloads.dtd (%chrome/browser/downloads/downloads.dtd)
locale/browser/downloads/downloads.properties (%chrome/browser/downloads/downloads.properties)
locale/browser/places/places.dtd (%chrome/browser/places/places.dtd)
@ -120,6 +121,7 @@
locale/browser/preferences/sync.dtd (%chrome/browser/preferences/sync.dtd)
#endif
locale/browser/preferences/tabs.dtd (%chrome/browser/preferences/tabs.dtd)
locale/browser/preferences/translation.dtd (%chrome/browser/preferences/translation.dtd)
#ifdef MOZ_SERVICES_SYNC
locale/browser/syncBrand.dtd (%chrome/browser/syncBrand.dtd)
locale/browser/syncSetup.dtd (%chrome/browser/syncSetup.dtd)

View File

@ -158,8 +158,8 @@ browser.jar:
skin/classic/browser/social/chat-icons.png (social/chat-icons.png)
skin/classic/browser/social/gear_default.png (../shared/social/gear_default.png)
skin/classic/browser/social/gear_clicked.png (../shared/social/gear_clicked.png)
skin/classic/browser/tabbrowser/connecting.png (../shared/tabbrowser/connecting.png)
skin/classic/browser/tabbrowser/loading.png (../shared/tabbrowser/loading.png)
skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png)
skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png)
skin/classic/browser/tabbrowser/tab.png (tabbrowser/tab.png)
skin/classic/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png)
skin/classic/browser/tabbrowser/tab-background-end.png (tabbrowser/tab-background-end.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -2897,10 +2897,6 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
@media (min-resolution: 2dppx) {
.tab-close-button > .toolbarbutton-icon {
width: 16px;
}
.tab-close-button.close-icon:not([selected=true]):not(:hover):-moz-lwtheme-brighttext {
-moz-image-region: rect(0, 128px, 32px, 96px);
}
@ -4342,10 +4338,6 @@ window > chatbox {
width: 24px;
}
.customization-tipPanel-closeBox > .close-icon > .toolbarbutton-icon {
width: 16px;
}
.customization-tipPanel-infoBox {
background-image: url(chrome://browser/skin/customizableui/info-icon-customizeTip@2x.png);
background-size: 25px 25px;

View File

@ -256,10 +256,10 @@ browser.jar:
skin/classic/browser/tabbrowser/newtab@2x.png (tabbrowser/newtab@2x.png)
skin/classic/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png)
skin/classic/browser/tabbrowser/newtab-inverted@2x.png (tabbrowser/newtab-inverted@2x.png)
skin/classic/browser/tabbrowser/connecting.png (../shared/tabbrowser/connecting.png)
skin/classic/browser/tabbrowser/connecting@2x.png (../shared/tabbrowser/connecting@2x.png)
skin/classic/browser/tabbrowser/loading.png (../shared/tabbrowser/loading.png)
skin/classic/browser/tabbrowser/loading@2x.png (../shared/tabbrowser/loading@2x.png)
skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png)
skin/classic/browser/tabbrowser/connecting@2x.png (tabbrowser/connecting@2x.png)
skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png)
skin/classic/browser/tabbrowser/loading@2x.png (tabbrowser/loading@2x.png)
skin/classic/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png)
skin/classic/browser/tabbrowser/tab-active-middle@2x.png (tabbrowser/tab-active-middle@2x.png)
skin/classic/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -926,20 +926,23 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
background-clip: padding-box !important;
background-color: hsla(210,25%,98%,.08) !important;
padding: 6px !important;
border-color: hsla(210,4%,10%,.25) !important;
transition-property: background-color, border-color !important;
border-style: none !important;
box-shadow: 0 1px 0 0 hsla(210,4%,10%,.25),
0 0 0 1px hsla(210,4%,10%,.25);
transition-property: background-color, box-shadow !important;
transition-duration: 250ms !important;
}
#back-button:not([disabled="true"]):not([open="true"]):not(:active):hover > .toolbarbutton-icon {
background-color: hsla(210,4%,10%,.08) !important;
box-shadow: none !important;
}
#back-button:not([disabled="true"]):hover:active > .toolbarbutton-icon,
#back-button[open="true"] > .toolbarbutton-icon {
background-color: hsla(210,4%,10%,.12) !important;
box-shadow: 0 1px 0 0 hsla(210,80%,20%,.1) inset !important;
box-shadow: 0 1px 0 0 hsla(210,4%,10%,.25),
0 0 0 1px hsla(210,4%,10%,.25),
0 1px 0 0 hsla(210,80%,20%,.1) inset !important;
}
%ifdef WINDOWS_AERO
@ -947,13 +950,11 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
(-moz-os-version: windows-win7) {
%endif
#back-button > .toolbarbutton-icon {
border: none !important;
background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1)) !important;
box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
0 0 0 1px hsla(0,0%,100%,.3) inset,
0 0 0 1px hsla(210,54%,20%,.25),
0 1px 0 hsla(210,54%,20%,.35) !important;
transition-property: background-color, box-shadow !important;
}
#back-button:not([disabled="true"]):not([open="true"]):not(:active):hover > .toolbarbutton-icon {

View File

@ -183,8 +183,8 @@ browser.jar:
skin/classic/browser/social/gear_clicked.png (../shared/social/gear_clicked.png)
skin/classic/browser/tabbrowser/newtab.png (tabbrowser/newtab.png)
skin/classic/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png)
skin/classic/browser/tabbrowser/connecting.png (../shared/tabbrowser/connecting.png)
skin/classic/browser/tabbrowser/loading.png (../shared/tabbrowser/loading.png)
skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png)
skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png)
skin/classic/browser/tabbrowser/tab.png (tabbrowser/tab.png)
skin/classic/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png)
skin/classic/browser/tabbrowser/tab-active-middle@2x.png (tabbrowser/tab-active-middle@2x.png)
@ -555,8 +555,8 @@ browser.jar:
skin/classic/aero/browser/social/gear_clicked.png (../shared/social/gear_clicked.png)
skin/classic/aero/browser/tabbrowser/newtab.png (tabbrowser/newtab.png)
skin/classic/aero/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png)
skin/classic/aero/browser/tabbrowser/connecting.png (../shared/tabbrowser/connecting.png)
skin/classic/aero/browser/tabbrowser/loading.png (../shared/tabbrowser/loading.png)
skin/classic/aero/browser/tabbrowser/connecting.png (tabbrowser/connecting.png)
skin/classic/aero/browser/tabbrowser/loading.png (tabbrowser/loading.png)
skin/classic/aero/browser/tabbrowser/tab.png (tabbrowser/tab.png)
skin/classic/aero/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png)
skin/classic/aero/browser/tabbrowser/tab-active-middle@2x.png (tabbrowser/tab-active-middle@2x.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -7640,6 +7640,19 @@ if test "$MOZ_CHROME_FILE_FORMAT" != "jar" &&
AC_MSG_ERROR([--enable-chrome-format must be set to either jar, flat, or omni])
fi
dnl ========================================================
dnl = Enable Support for devtools webide
dnl ========================================================
MOZ_ARG_ENABLE_BOOL(devtools-webide,
[ --enable-devtools-webide Set compile flags necessary for compiling devtools webide ],
MOZ_DEVTOOLS_WEBIDE=1,
MOZ_DEVTOOLS_WEBIDE= )
if test -n "$MOZ_DEVTOOLS_WEBIDE"; then
AC_DEFINE(MOZ_DEVTOOLS_WEBIDE)
fi
AC_SUBST(MOZ_DEVTOOLS_WEBIDE)
dnl =========================================================
dnl Omnijar packaging (bug 552121)
dnl =========================================================

View File

@ -89,7 +89,52 @@ function runTests() {
}
const NDEF = {
TNF_WELL_KNOWN: 1
TNF_WELL_KNOWN: 1,
// compares two NDEF messages
compare: function(ndef1, ndef2) {
isnot(ndef1, null, "LHS message is not null");
isnot(ndef2, null, "RHS message is not null");
is(ndef1.length, ndef2.length,
"NDEF messages have the same number of records");
ndef1.forEach(function(record1, index) {
let record2 = this[index];
is(record1.tnf, record2.tnf, "test for equal TNF fields");
let fields = ["type", "id", "payload"];
fields.forEach(function(value) {
let field1 = record1[value];
let field2 = record2[value];
is(field1.length, field2.length,
value + " fields have the same length");
let eq = true;
for (let i = 0; eq && i < field1.length; ++i) {
eq = (field1[i] === field2[i]);
}
ok(eq, value + " fields contain the same data");
});
}, ndef2);
},
// parses an emulator output string into an NDEF message
parseString: function(str) {
// make it an object
let arr = null;
try {
arr = JSON.parse(str);
} catch (e) {
ok(false, "Parser error: " + e.message);
return null;
}
// and build NDEF array
let ndef = arr.map(function(value) {
let type = new Uint8Array(NfcUtils.fromUTF8(this.atob(value.type)));
let id = new Uint8Array(NfcUtils.fromUTF8(this.atob(value.id)));
let payload =
new Uint8Array(NfcUtils.fromUTF8(this.atob(value.payload)));
return new MozNDEFRecord(value.tnf, type, id, payload);
}, window);
return ndef;
}
};
var NfcUtils = {

View File

@ -6,46 +6,6 @@ MARIONETTE_HEAD_JS = "head.js";
let url = "https://www.example.com";
function compareNDEFs(ndef1, ndef2) {
is(ndef1.length, ndef2.length,
"NDEF messages have the same number of records");
ndef1.forEach(function(record1, index) {
let record2 = this[index];
is(record1.tnf, record2.tnf, "test for equal TNF fields");
let fields = ["type", "id", "payload"];
fields.forEach(function(value) {
let field1 = record1[value];
let field2 = record2[value];
is(field1.length, field2.length,
value + " fields have the same length");
let eq = true;
for (let i = 0; eq && i < field1.length; ++i) {
eq = (field1[i] === field2[i]);
}
ok(eq, value + " fields contain the same data");
});
}, ndef2);
}
function parseNDEFString(str) {
/* make it an object */
let arr = null;
try {
arr = JSON.parse(str);
} catch (e) {
ok(false, "Parser error: " + e.message);
return null;
}
/* and build NDEF array */
let ndef = arr.map(function(value) {
let type = new Uint8Array(NfcUtils.fromUTF8(this.atob(value.type)));
let id = new Uint8Array(NfcUtils.fromUTF8(this.atob(value.id)));
let payload = new Uint8Array(NfcUtils.fromUTF8(this.atob(value.payload)));
return new MozNDEFRecord(value.tnf, type, id, payload);
}, window);
return ndef;
}
function sendNDEF(techType, sessionToken) {
let tnf = NDEF.TNF_WELL_KNOWN;
let type = new Uint8Array(NfcUtils.fromUTF8("U"));
@ -62,8 +22,7 @@ function sendNDEF(techType, sessionToken) {
log("Executing \'" + cmd + "\'");
emulator.run(cmd, function(result) {
is(result.pop(), "OK", "check SNEP PUT result");
let ndef2 = parseNDEFString(result.pop());
compareNDEFs(ndef, ndef2);
NDEF.compare(ndef, NDEF.parseString(result.pop()));
toggleNFC(false, runNextTest);
});
};

View File

@ -5,6 +5,7 @@
package org.mozilla.gecko.db;
import java.util.ArrayList;
import java.util.List;
import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
@ -160,6 +161,13 @@ public class BrowserDB {
return sDb.filter(cr, constraint, limit);
}
private static void appendUrlsFromCursor(List<String> urls, Cursor c) {
c.moveToPosition(-1);
while (c.moveToNext()) {
urls.add(c.getString(c.getColumnIndex(URLColumns.URL)));
};
}
public static Cursor getTopSites(ContentResolver cr, int minLimit, int maxLimit) {
// Note this is not a single query anymore, but actually returns a mixture
// of two queries, one for topSites and one for pinned sites.
@ -167,12 +175,17 @@ public class BrowserDB {
int pinnedCount = pinnedSites.getCount();
Cursor topSites = sDb.getTopSites(cr, maxLimit - pinnedCount);
int topCount = topSites.getCount();
Cursor suggestedSites = null;
if (sSuggestedSites != null) {
final int count = minLimit - pinnedCount - topSites.getCount();
final int count = minLimit - pinnedCount - topCount;
if (count > 0) {
suggestedSites = sSuggestedSites.get(count);
final List<String> excludeUrls = new ArrayList<String>(pinnedCount + topCount);
appendUrlsFromCursor(excludeUrls, pinnedSites);
appendUrlsFromCursor(excludeUrls, topSites);
suggestedSites = sSuggestedSites.get(count, excludeUrls);
}
}

View File

@ -156,6 +156,16 @@ public class SuggestedSites {
* @param limit maximum number of suggested sites.
*/
public Cursor get(int limit) {
return get(limit, null);
}
/**
* Returns a {@code Cursor} with the list of suggested websites.
*
* @param limit maximum number of suggested sites.
* @param excludeUrls list of URLs to be excluded from the list.
*/
public Cursor get(int limit, List<String> excludeUrls) {
List<Site> sites = cachedSites.get();
if (sites == null) {
Log.d(LOGTAG, "No cached sites, refreshing.");
@ -177,6 +187,10 @@ public class SuggestedSites {
for (int i = 0; i < count; i++) {
final Site site = sites.get(i);
if (excludeUrls != null && excludeUrls.contains(site.url)) {
continue;
}
final RowBuilder row = cursor.newRow();
row.add(-1);
row.add(site.url);

View File

@ -157,25 +157,6 @@ public class TopSitesCursorWrapper implements Cursor {
return (c != null && !c.isBeforeFirst() && !c.isAfterLast());
}
private void updateRowState() {
if (cursorHasValidPosition(pinnedCursor)) {
currentRowType = RowType.PINNED;
currentCursor = pinnedCursor;
} else if (cursorHasValidPosition(topCursor)) {
currentRowType = RowType.TOP;
currentCursor = topCursor;
} else if (cursorHasValidPosition(suggestedCursor)) {
currentRowType = RowType.SUGGESTED;
currentCursor = suggestedCursor;
} else if (currentPosition >= 0 && currentPosition < minSize) {
currentRowType = RowType.BLANK;
currentCursor = null;
} else {
currentRowType = RowType.UNKNOWN;
currentCursor = null;
}
}
private void updatePinnedBefore(int position) {
pinnedBefore = 0;
for (int i = 0; i < position; i++) {
@ -185,40 +166,51 @@ public class TopSitesCursorWrapper implements Cursor {
}
}
private void updateTopCursorPosition(int position) {
// Move the real cursor as if we were stepping through it to this position.
// Account for pinned sites, and be careful to update its position to the
// minimum or maximum position, even if we're moving beyond its bounds.
final int actualPosition = position - pinnedBefore;
if (actualPosition <= -1) {
topCursor.moveToPosition(-1);
} else if (actualPosition >= topCursor.getCount()) {
topCursor.moveToPosition(topCursor.getCount());
} else {
topCursor.moveToPosition(actualPosition);
}
private void setCurrentCursor(Cursor cursor, int position, RowType rowType) {
cursor.moveToPosition(position);
currentRowType = rowType;
currentCursor = cursor;
}
private void updatePinnedCursorPosition(int position) {
private boolean updateTopCursorPosition(int position) {
final int index = position - pinnedBefore;
if (index >= 0 && index < topCursor.getCount()) {
setCurrentCursor(topCursor, index, RowType.TOP);
return true;
}
return false;
}
private boolean updatePinnedCursorPosition(int position) {
if (position >= minSize) {
return false;
}
if (pinnedPositions.get(position)) {
pinnedCursor.moveToPosition(pinnedPositions.indexOfKey(position));
} else {
pinnedCursor.moveToPosition(-1);
setCurrentCursor(pinnedCursor, pinnedPositions.indexOfKey(position), RowType.PINNED);
return true;
}
return false;
}
private void updateSuggestedCursorPosition(int position) {
private boolean updateSuggestedCursorPosition(int position) {
if (suggestedCursor == null) {
return;
return false;
}
final int index = Math.max(-1, position - pinnedBefore - topCursor.getCount());
if (index < suggestedCursor.getCount()) {
suggestedCursor.moveToPosition(index);
} else {
suggestedCursor.moveToPosition(-1);
if (position >= minSize) {
return false;
}
final int index = position - pinnedBefore - topCursor.getCount();
if (index >= 0 && index < suggestedCursor.getCount()) {
setCurrentCursor(suggestedCursor, index, RowType.SUGGESTED);
return true;
}
return false;
}
private void assertValidColumnIndex(int columnIndex) {
@ -318,12 +310,19 @@ public class TopSitesCursorWrapper implements Cursor {
@Override
public boolean moveToPosition(int position) {
currentPosition = position;
updatePinnedBefore(position);
updatePinnedCursorPosition(position);
updateTopCursorPosition(position);
updateSuggestedCursorPosition(position);
updateRowState();
if (!updatePinnedCursorPosition(position) &&
!updateTopCursorPosition(position) &&
!updateSuggestedCursorPosition(position)) {
if (position >= 0 && position < minSize) {
currentRowType = RowType.BLANK;
} else {
currentRowType = RowType.UNKNOWN;
}
currentCursor = null;
}
return cursorHasValidPosition(this);
}

View File

@ -63,6 +63,9 @@ public class DynamicPanel extends HomeFragment {
// Dataset ID to be used by the loader
private static final String DATASET_REQUEST = "dataset_request";
// Max number of items to display in the panel
private static final int RESULT_LIMIT = 100;
// The main view for this fragment. This contains the PanelLayout and PanelAuthLayout.
private FrameLayout mView;
@ -381,6 +384,8 @@ public class DynamicPanel extends HomeFragment {
final Uri queryUri = HomeItems.CONTENT_URI.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_DATASET_ID,
mRequest.getDatasetId())
.appendQueryParameter(BrowserContract.PARAM_LIMIT,
String.valueOf(RESULT_LIMIT))
.build();
// XXX: You can use HomeItems.CONTENT_FAKE_URI for development

View File

@ -78,9 +78,6 @@ class PanelItemView extends LinearLayout {
private ArticleItemView(Context context) {
super(context, R.layout.panel_article_item);
setOrientation(LinearLayout.HORIZONTAL);
final int padding = getResources().getDimensionPixelSize(R.dimen.article_item_view_padding);
setPadding(0, padding, 0, padding);
}
}

View File

@ -6,16 +6,15 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView android:id="@+id/image"
android:layout_width="54dp"
android:layout_height="44dp"
android:layout_marginLeft="10dip"
android:layout_width="@dimen/panel_article_item_height"
android:layout_height="@dimen/panel_article_item_height"
android:scaleType="centerCrop"/>
<LinearLayout android:id="@+id/title_desc_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:paddingRight="10dip"
android:layout_height="@dimen/panel_article_item_height"
android:paddingLeft="15dip"
android:paddingRight="15dip"
android:gravity="center_vertical"
android:orientation="vertical">
@ -28,6 +27,7 @@
style="@style/Widget.PanelItemView.Description"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:maxLength="1024"/>
</LinearLayout>

View File

@ -108,6 +108,6 @@
<!-- PanelGridView dimensions -->
<dimen name="panel_grid_view_column_width">150dp</dimen>
<!-- ArticleItemView dimensions -->
<dimen name="article_item_view_padding">15dp</dimen>
<!-- PanelItemView dimensions -->
<dimen name="panel_article_item_height">95dp</dimen>
</resources>

View File

@ -364,7 +364,7 @@
<style name="TextAppearance.Widget.Home.ItemTitle" parent="TextAppearance.Medium"/>
<style name="TextAppearance.Widget.Home.ItemDescription" parent="TextAppearance.Micro">
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textColor">#AFB1B3</item>
</style>
<style name="TextAppearance.Widget.HomeBanner" parent="TextAppearance.Small">

View File

@ -10,26 +10,18 @@ import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.R;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.GeckoPopupMenu;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@ -37,12 +29,11 @@ import android.widget.LinearLayout;
import java.util.UUID;
import java.util.ArrayList;
public class PageActionLayout extends LinearLayout implements GeckoEventListener,
public class PageActionLayout extends LinearLayout implements NativeEventListener,
View.OnClickListener,
View.OnLongClickListener {
private final String LOGTAG = "GeckoPageActionLayout";
private final String MENU_BUTTON_KEY = "MENU_BUTTON_KEY";
private final int DEFAULT_PAGE_ACTIONS_SHOWN = 2;
private static final String MENU_BUTTON_KEY = "MENU_BUTTON_KEY";
private static final int DEFAULT_PAGE_ACTIONS_SHOWN = 2;
private ArrayList<PageAction> mPageActionList;
private GeckoPopupMenu mPageActionsMenu;
@ -83,33 +74,29 @@ public class PageActionLayout extends LinearLayout implements GeckoEventListener
}
@Override
public void handleMessage(String event, JSONObject message) {
try {
if (event.equals("PageActions:Add")) {
final String id = message.getString("id");
final String title = message.getString("title");
final String imageURL = message.optString("icon");
final boolean mImportant = message.getBoolean("important");
public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
if (event.equals("PageActions:Add")) {
final String id = message.getString("id");
final String title = message.getString("title");
final String imageURL = message.optString("icon", null);
final boolean mImportant = message.getBoolean("important");
addPageAction(id, title, imageURL, new OnPageActionClickListeners() {
@Override
public void onClick(String id) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:Clicked", id));
}
addPageAction(id, title, imageURL, new OnPageActionClickListeners() {
@Override
public void onClick(String id) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:Clicked", id));
}
@Override
public boolean onLongClick(String id) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:LongClicked", id));
return true;
}
}, mImportant);
} else if (event.equals("PageActions:Remove")) {
final String id = message.getString("id");
@Override
public boolean onLongClick(String id) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:LongClicked", id));
return true;
}
}, mImportant);
} else if (event.equals("PageActions:Remove")) {
final String id = message.getString("id");
removePageAction(id);
}
} catch(JSONException ex) {
Log.e(LOGTAG, "Error deocding", ex);
removePageAction(id);
}
}

View File

@ -24,6 +24,9 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
*/
const SCHEMA_VERSION = 2;
// The maximum number of items you can attempt to save at once.
const MAX_SAVE_COUNT = 100;
XPCOMUtils.defineLazyGetter(this, "DB_PATH", function() {
return OS.Path.join(OS.Constants.Path.profileDir, "home.sqlite");
});
@ -281,21 +284,65 @@ function validateItem(datasetId, item) {
}
}
var gRefreshTimers = {};
/**
* Sends a message to Java to refresh the given dataset. Delays sending
* messages to avoid successive refreshes, which can result in flashing views.
*/
function refreshDataset(datasetId) {
// Bail if there's already a refresh timer waiting to fire
if (gRefreshTimers[datasetId]) {
return;
}
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(function(timer) {
delete gRefreshTimers[datasetId];
sendMessageToJava({
type: "HomePanels:RefreshDataset",
datasetId: datasetId
});
}, 100, Ci.nsITimer.TYPE_ONE_SHOT);
gRefreshTimers[datasetId] = timer;
}
HomeStorage.prototype = {
/**
* Saves data rows to the DB.
*
* @param data
* (array) JSON array of row items
* An array of JS objects represnting row items to save.
* Each object may have the following properties:
* - url (string)
* - title (string)
* - description (string)
* - image_url (string)
* - filter (string)
* @param options
* A JS object holding additional cofiguration properties.
* The following properties are currently supported:
* - replace (boolean): Whether or not to replace existing items.
*
* @return Promise
* @resolves When the operation has completed.
*/
save: function(data) {
save: function(data, options) {
if (data && data.length > MAX_SAVE_COUNT) {
throw "save failed for dataset = " + this.datasetId +
": you cannot save more than " + MAX_SAVE_COUNT + " items at once";
}
return Task.spawn(function save_task() {
let db = yield getDatabaseConnection();
try {
yield db.executeTransaction(function save_transaction() {
if (options && options.replace) {
yield db.executeCached(SQL.deleteFromDataset, { dataset_id: this.datasetId });
}
// Insert data into DB.
for (let item of data) {
validateItem(this.datasetId, item);
@ -317,10 +364,7 @@ HomeStorage.prototype = {
yield db.close();
}
sendMessageToJava({
type: "HomePanels:RefreshDataset",
datasetId: this.datasetId,
});
refreshDataset(this.datasetId);
}.bind(this));
},
@ -340,10 +384,7 @@ HomeStorage.prototype = {
yield db.close();
}
sendMessageToJava({
type: "HomePanels:RefreshDataset",
datasetId: this.datasetId,
});
refreshDataset(this.datasetId);
}.bind(this));
}
};

View File

@ -11,6 +11,8 @@ import android.test.mock.MockResources;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
@ -140,4 +142,29 @@ public class TestSuggestedSites extends BrowserTestCase {
c.close();
}
public void testExcludeUrls() {
resources.setSuggestedSitesResource(generateSites(6));
List<String> excludedUrls = new ArrayList<String>(3);
excludedUrls.add("url1");
excludedUrls.add("url3");
excludedUrls.add("url5");
List<String> includedUrls = new ArrayList<String>(3);
includedUrls.add("url0");
includedUrls.add("url2");
includedUrls.add("url4");
Cursor c = new SuggestedSites(context).get(DEFAULT_LIMIT, excludedUrls);
c.moveToPosition(-1);
while (c.moveToNext()) {
String url = c.getString(c.getColumnIndexOrThrow(BrowserContract.SuggestedSites.URL));
assertFalse(excludedUrls.contains(url));
assertTrue(includedUrls.contains(url));
}
c.close();
}
}

View File

@ -3,8 +3,6 @@ function test()
const kPrefName_AutoScroll = "general.autoScroll";
Services.prefs.setBoolPref(kPrefName_AutoScroll, false);
gBrowser.selectedTab = gBrowser.addTab();
var doc;
function startLoad(dataUri) {
@ -27,9 +25,6 @@ function test()
if (Services.prefs.prefHasUserValue(kPrefName_AutoScroll))
Services.prefs.clearUserPref(kPrefName_AutoScroll);
// cleaning-up
gBrowser.removeCurrentTab();
// waitForFocus() fixes a failure in the next test if the latter runs too soon.
waitForFocus(finish);
}

View File

@ -3,8 +3,6 @@ function test()
const kPrefName_AutoScroll = "general.autoScroll";
Services.prefs.setBoolPref(kPrefName_AutoScroll, true);
gBrowser.selectedTab = gBrowser.addTab();
const expectScrollNone = 0;
const expectScrollVert = 1;
const expectScrollHori = 2;
@ -126,9 +124,6 @@ function test()
if (Services.prefs.prefHasUserValue(kPrefName_AutoScroll))
Services.prefs.clearUserPref(kPrefName_AutoScroll);
// cleaning-up
gBrowser.removeCurrentTab();
// waitForFocus() fixes a failure in the next test if the latter runs too soon.
waitForFocus(finish);
}

Some files were not shown because too many files have changed in this diff Show More