merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2014-06-17 14:09:46 +02:00
commit 86bbfb82a8
121 changed files with 4426 additions and 893 deletions

View File

@ -15,6 +15,7 @@ const { loadReason } = require('../self');
const { rootURI, metadata } = require("@loader/options");
const globals = require('../system/globals');
const xulApp = require('../system/xul-app');
const { id } = require('sdk/self');
const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
getService(Ci.nsIAppShellService);
const { preferences } = metadata;
@ -134,7 +135,7 @@ function run(options) {
// native-options does stuff directly with preferences key from package.json
if (preferences && preferences.length > 0) {
try {
require('../preferences/native-options').enable(preferences);
require('../preferences/native-options').enable({ preferences: preferences, id: id });
}
catch (error) {
console.exception(error);

View File

@ -12,29 +12,41 @@ const { on } = require('../system/events');
const { id, preferencesBranch } = require('../self');
const { localizeInlineOptions } = require('../l10n/prefs');
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm");
const { defer } = require("sdk/core/promise");
const DEFAULT_OPTIONS_URL = 'data:text/xml,<placeholder/>';
const VALID_PREF_TYPES = ['bool', 'boolint', 'integer', 'string', 'color',
const VALID_PREF_TYPES = ['bool', 'boolint', 'integer', 'string', 'color',
'file', 'directory', 'control', 'menulist', 'radio'];
function enable(preferences) {
function enable({ preferences, id }) {
let enabled = defer();
validate(preferences);
setDefaults(preferences, preferencesBranch);
// allow the use of custom options.xul
AddonManager.getAddonByID(id, (addon) => {
if (addon.optionsURL === DEFAULT_OPTIONS_URL)
on('addon-options-displayed', onAddonOptionsDisplayed, true);
})
on('addon-options-displayed', onAddonOptionsDisplayed, true);
enabled.resolve({ id: id });
});
function onAddonOptionsDisplayed({ subject: doc, data }) {
if (data === id) {
let parent = doc.getElementById('detail-downloads').parentNode;
injectOptions(preferences, preferencesBranch, doc, parent);
injectOptions({
preferences: preferences,
preferencesBranch: preferencesBranch,
document: doc,
parent: parent,
id: id
});
localizeInlineOptions(doc);
}
}
return enabled.promise;
}
exports.enable = enable;
@ -75,18 +87,18 @@ function setDefaults(preferences, preferencesBranch) {
const branch = Cc['@mozilla.org/preferences-service;1'].
getService(Ci.nsIPrefService).
getDefaultBranch('extensions.' + preferencesBranch + '.');
for (let {name, value} of preferences) {
for (let { name, value } of preferences) {
switch (typeof value) {
case 'boolean':
branch.setBoolPref(name, value);
break;
case 'number':
case 'number':
// must be integer, ignore otherwise
if (value % 1 === 0)
if (value % 1 === 0) {
branch.setIntPref(name, value);
}
break;
case 'string':
// ∵
let str = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
str.data = value;
@ -98,11 +110,12 @@ function setDefaults(preferences, preferencesBranch) {
exports.setDefaults = setDefaults;
// dynamically injects inline options into about:addons page at runtime
function injectOptions(preferences, preferencesBranch, document, parent) {
function injectOptions({ preferences, preferencesBranch, document, parent, id }) {
for (let { name, type, hidden, title, description, label, options, on, off } of preferences) {
if (hidden)
if (hidden) {
continue;
}
let setting = document.createElement('setting');
setting.setAttribute('pref-name', name);
@ -114,7 +127,7 @@ function injectOptions(preferences, preferencesBranch, document, parent) {
if (type === 'file' || type === 'directory') {
setting.setAttribute('fullpath', 'true');
}
}
else if (type === 'control') {
let button = document.createElement('button');
button.setAttribute('pref-name', name);
@ -123,11 +136,11 @@ function injectOptions(preferences, preferencesBranch, document, parent) {
button.setAttribute('oncommand', "Services.obs.notifyObservers(null, '" +
id + "-cmdPressed', '" + name + "');");
setting.appendChild(button);
}
}
else if (type === 'boolint') {
setting.setAttribute('on', on);
setting.setAttribute('off', off);
}
}
else if (type === 'menulist') {
let menulist = document.createElement('menulist');
let menupopup = document.createElement('menupopup');
@ -139,7 +152,7 @@ function injectOptions(preferences, preferencesBranch, document, parent) {
}
menulist.appendChild(menupopup);
setting.appendChild(menulist);
}
}
else if (type === 'radio') {
let radiogroup = document.createElement('radiogroup');
for (let { value, label } of options) {

View File

@ -1,7 +1,6 @@
/* 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";
module.metadata = {

View File

@ -0,0 +1,50 @@
/* 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";
module.metadata = {
"stability": "unstable"
};
const { openTab, getBrowserForTab, getTabId } = require("sdk/tabs/utils");
const { defer, all } = require("sdk/core/promise");
const { on, off } = require("sdk/system/events");
const { setTimeout } = require("sdk/timers");
const { getMostRecentBrowserWindow } = require('../window/utils');
const open = function open({ id }) {
let showing = defer();
let loaded = defer();
let result = { id: id };
let tab = openTab(getMostRecentBrowserWindow(), "about:addons", {
inBackground: true
});
let browser = getBrowserForTab(tab);
browser.addEventListener("load", function onPageLoad() {
browser.removeEventListener("load", onPageLoad, true);
let window = browser.contentWindow;
// wait for the add-on's "addon-options-displayed"
on("addon-options-displayed", function onPrefDisplayed({ subject: doc, data }) {
if (data === id) {
off("addon-options-displayed", onPrefDisplayed);
result.tabId = getTabId(tab);
result.document = doc;
loaded.resolve();
}
}, true);
// display the add-on inline preferences page
window.gViewController.commands.cmd_showItemDetails.doCommand({ id: id }, true);
let { node } = window.gViewController.viewObjects.detail;
node.addEventListener("ViewChanged", function whenViewChanges() {
node.removeEventListener("ViewChanged", whenViewChanges, false);
showing.resolve();
}, false);
}, true);
return all([ showing.promise, loaded.promise ]).then(_ => result);
}
exports.open = open;

View File

@ -266,7 +266,7 @@ parser_groups = (
help="enable remote windows",
action="store_true",
default=False,
cmds=['test', 'run', 'testex', 'testpkgs',
cmds=['test', 'run', 'testex', 'testpkgs',
'testaddons', 'testcfx', 'testall'])),
(("", "--logfile",), dict(dest="logfile",
help="log console output to file",
@ -421,13 +421,14 @@ def test_all_testaddons(env_root, defaults):
addons.sort()
fail = False
for dirname in addons:
# apply the filter
if (not defaults['filter'].split(":")[0] in dirname):
continue
print >>sys.stderr, "Testing %s..." % dirname
sys.stderr.flush()
try:
run(arguments=["run",
run(arguments=["testrun",
"--pkgdir",
os.path.join(addons_dir, dirname)],
defaults=defaults,
@ -619,7 +620,7 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
return
test_cfx(env_root, options.verbose)
return
elif command not in ["xpi", "test", "run"]:
elif command not in ["xpi", "test", "run", "testrun"]:
print >>sys.stderr, "Unknown command: %s" % command
print >>sys.stderr, "Try using '--help' for assistance."
sys.exit(1)
@ -663,6 +664,9 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
enforce_timeouts = True
elif command == "run":
use_main = True
elif command == "testrun":
use_main = True
enforce_timeouts = True
else:
assert 0, "shouldn't get here"
@ -681,7 +685,7 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
# TODO: Consider keeping a cache of dynamic UUIDs, based
# on absolute filesystem pathname, in the root directory
# or something.
if command in ('xpi', 'run'):
if command in ('xpi', 'run', 'testrun'):
from cuddlefish.preflight import preflight_config
if target_cfg_json:
config_was_ok, modified = preflight_config(target_cfg,

View File

@ -37,10 +37,12 @@ import re
import os.path
def get_expanded_variables(versionfile_source):
# the code embedded in _version.py can just fetch the value of these
# variables. When used from setup.py, we don't want to import
# _version.py, so we do it with a regexp instead. This function is not
# used from _version.py.
"""
the code embedded in _version.py can just fetch the value of these
variables. When used from setup.py, we don't want to import
_version.py, so we do it with a regexp instead. This function is not
used from _version.py.
"""
variables = {}
try:
for line in open(versionfile_source,"r").readlines():
@ -81,15 +83,16 @@ def versions_from_expanded_variables(variables, tag_prefix):
"full": variables["full"].strip() }
def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
# this runs 'git' from the root of the source tree. That either means
# someone ran a setup.py command (and this code is in versioneer.py, thus
# the containing directory is the root of the source tree), or someone
# ran a project-specific entry point (and this code is in _version.py,
# thus the containing directory is somewhere deeper in the source tree).
# This only gets called if the git-archive 'subst' variables were *not*
# expanded, and _version.py hasn't already been rewritten with a short
# version string, meaning we're inside a checked out source tree.
"""
this runs 'git' from the root of the source tree. That either means
someone ran a setup.py command (and this code is in versioneer.py, thus
the containing directory is the root of the source tree), or someone
ran a project-specific entry point (and this code is in _version.py,
thus the containing directory is somewhere deeper in the source tree).
This only gets called if the git-archive 'subst' variables were *not*
expanded, and _version.py hasn't already been rewritten with a short
version string, meaning we're inside a checked out source tree.
"""
try:
here = os.path.abspath(__file__)
except NameError:

View File

@ -120,7 +120,9 @@ def hash_file(fn):
return hashlib.sha256(open(fn,"rb").read()).hexdigest()
def get_datafiles(datadir):
# yields pathnames relative to DATADIR, ignoring some files
"""
yields pathnames relative to DATADIR, ignoring some files
"""
for dirpath, dirnames, filenames in os.walk(datadir):
filenames = list(filter_filenames(filenames))
# this tells os.walk to prune the search
@ -193,8 +195,9 @@ class ManifestBuilder:
self.test_modules = [] # for runtime
def build(self, scan_tests, test_filter_re):
# process the top module, which recurses to process everything it
# reaches
"""
process the top module, which recurses to process everything it reaches
"""
if "main" in self.target_cfg:
top_mi = self.find_top(self.target_cfg)
top_me = self.process_module(top_mi)
@ -261,9 +264,11 @@ class ManifestBuilder:
return sorted(used)
def get_used_files(self, bundle_sdk_modules):
# returns all .js files that we reference, plus data/ files. You will
# need to add the loader, off-manifest files that it needs, and
# generated metadata.
"""
returns all .js files that we reference, plus data/ files. You will
need to add the loader, off-manifest files that it needs, and
generated metadata.
"""
for datamap in self.datamaps.values():
for (zipname, absname) in datamap.files_to_copy:
yield absname

View File

@ -715,7 +715,7 @@ def run_app(harness_root_dir, manifest_rdf, harness_options,
done = False
result = None
test_name = "unknown"
test_name = "Jetpack startup"
def Timeout(message, test_name, parseable):
if parseable:

View File

@ -25,10 +25,13 @@ UNICODE_ORG_XML_URL = "http://unicode.org/repos/cldr/trunk/common/supplemental/p
CONDITION_RE = r'n( mod \d+)? (is|in|within|(not in))( not)? ([^\s]+)'
# For a given regexp.MatchObject `g` for `CONDITION_RE`,
# returns the equivalent JS piece of code
# i.e. maps pseudo conditional language from unicode.org XML to JS code
def parseCondition(g):
"""
For a given regexp.MatchObject `g` for `CONDITION_RE`,
returns the equivalent JS piece of code
i.e. maps pseudo conditional language from unicode.org XML to JS code
"""
lvalue = "n"
if g.group(1):
lvalue = "(n %% %d)" % int(g.group(1).replace("mod ", ""))
@ -81,7 +84,9 @@ def parseCondition(g):
return "%s(%s)" % (notPrefix, " || ".join(subCondition))
def computeRules():
# Fetch plural rules data directly from unicode.org website:
"""
Fetch plural rules data directly from unicode.org website:
"""
url = UNICODE_ORG_XML_URL
f = urllib2.urlopen(url)
doc = xml.dom.minidom.parse(f)

View File

@ -28,9 +28,10 @@ INFINITY = float('1e66666')
FLOAT_REPR = repr
def floatstr(o, allow_nan=True):
# Check for specials. Note that this type of test is processor- and/or
# platform-specific, so do tests which don't depend on the internals.
"""
Check for specials. Note that this type of test is processor- and/or
platform-specific, so do tests which don't depend on the internals.
"""
if o != o:
text = 'NaN'
elif o == INFINITY:
@ -174,9 +175,15 @@ class JSONEncoder(object):
self.encoding = encoding
def _newline_indent(self):
"""
Indent lines by level
"""
return '\n' + (' ' * (self.indent * self.current_indent_level))
def _iterencode_list(self, lst, markers=None):
"""
Encoding lists, yielding by level
"""
if not lst:
yield '[]'
return
@ -210,6 +217,9 @@ class JSONEncoder(object):
del markers[markerid]
def _iterencode_dict(self, dct, markers=None):
"""
Encoding dictionaries, yielding by level
"""
if not dct:
yield '{}'
return

View File

@ -6,9 +6,10 @@
const { get: getPref } = require('sdk/preferences/service');
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { openTab, closeTab, getBrowserForTab } = require('sdk/tabs/utils');
const tabs = require('sdk/tabs');
exports.testRemotePrefIsSet = function(assert) {
assert.ok(getPref('browser.tabs.remote.autostart'),
assert.ok(getPref('browser.tabs.remote.autostart'),
"Electrolysis remote tabs pref should be set");
}
@ -19,11 +20,12 @@ exports.testTabIsRemote = function(assert, done) {
// can't simply close a remote tab before it is loaded, bug 1006043
let mm = getBrowserForTab(tab).messageManager;
mm.addMessageListener(7, function() {
mm.addMessageListener('7', function listener() {
mm.removeMessageListener('7', listener);
tabs.once('close', done);
closeTab(tab);
done();
})
mm.loadFrameScript('data:,sendAsyncMessage(7)', false);
mm.loadFrameScript('data:,sendAsyncMessage("7")', true);
}
require('sdk/test/runner').runTestsFromModule(module);

View File

@ -7,8 +7,11 @@ const { Cu } = require('chrome');
const sp = require('sdk/simple-prefs');
const app = require('sdk/system/xul-app');
const self = require('sdk/self');
const tabs = require('sdk/tabs');
const { preferencesBranch } = require('sdk/self');
const { preferencesBranch } = self;
const { open } = require('sdk/preferences/utils');
const { getTabForId } = require('sdk/tabs/utils');
const { Tab } = require('sdk/tabs/tab');
require('sdk/tabs');
const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
@ -26,112 +29,75 @@ exports.testOptionsType = function(assert, done) {
}
exports.testButton = function(assert, done) {
tabs.open({
url: 'about:addons',
onReady: function(tab) {
sp.once('sayHello', function() {
assert.pass('The button was pressed!');
tab.close(done)
});
open(self).then(({ tabId, document }) => {
let tab = Tab({ tab: getTabForId(tabId) });
sp.once('sayHello', _ => {
assert.pass('The button was pressed!');
tab.close(done);
});
tab.attach({
contentScriptWhen: 'end',
contentScript: 'function onLoad() {\n' +
'unsafeWindow.removeEventListener("load", onLoad, false);\n' +
'AddonManager.getAddonByID("' + self.id + '", function(aAddon) {\n' +
'unsafeWindow.gViewController.viewObjects.detail.node.addEventListener("ViewChanged", function whenViewChanges() {\n' +
'unsafeWindow.gViewController.viewObjects.detail.node.removeEventListener("ViewChanged", whenViewChanges, false);\n' +
'setTimeout(function() {\n' + // TODO: figure out why this is necessary..
'unsafeWindow.document.querySelector("button[label=\'Click me!\']").click()\n' +
'}, 250);\n' +
'}, false);\n' +
'unsafeWindow.gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);\n' +
'});\n' +
'}\n' +
// Wait for the load event ?
'if (document.readyState == "complete") {\n' +
'onLoad()\n' +
'} else {\n' +
'unsafeWindow.addEventListener("load", onLoad, false);\n' +
'}\n',
});
}
tab.attach({
contentScript: 'unsafeWindow.document.querySelector("button[label=\'Click me!\']").click();'
});
});
}
if (app.is('Firefox')) {
exports.testAOM = function(assert, done) {
tabs.open({
url: 'about:addons',
onReady: function(tab) {
tab.attach({
contentScriptWhen: 'end',
contentScript: 'function onLoad() {\n' +
'unsafeWindow.removeEventListener("load", onLoad, false);\n' +
'AddonManager.getAddonByID("' + self.id + '", function(aAddon) {\n' +
'unsafeWindow.gViewController.viewObjects.detail.node.addEventListener("ViewChanged", function whenViewChanges() {\n' +
'unsafeWindow.gViewController.viewObjects.detail.node.removeEventListener("ViewChanged", whenViewChanges, false);\n' +
'setTimeout(function() {\n' + // TODO: figure out why this is necessary..
'self.postMessage({\n' +
'someCount: unsafeWindow.document.querySelectorAll("setting[title=\'some-title\']").length,\n' +
'somePreference: getAttributes(unsafeWindow.document.querySelector("setting[title=\'some-title\']")),\n' +
'myInteger: getAttributes(unsafeWindow.document.querySelector("setting[title=\'my-int\']")),\n' +
'myHiddenInt: getAttributes(unsafeWindow.document.querySelector("setting[title=\'hidden-int\']")),\n' +
'sayHello: getAttributes(unsafeWindow.document.querySelector("button[label=\'Click me!\']"))\n' +
'});\n' +
'}, 250);\n' +
'}, false);\n' +
'unsafeWindow.gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);\n' +
'});\n' +
'function getAttributes(ele) {\n' +
'if (!ele) return {};\n' +
'return {\n' +
'pref: ele.getAttribute("pref"),\n' +
'type: ele.getAttribute("type"),\n' +
'title: ele.getAttribute("title"),\n' +
'desc: ele.getAttribute("desc"),\n' +
'"data-jetpack-id": ele.getAttribute(\'data-jetpack-id\')\n' +
'}\n' +
'}\n' +
'}\n' +
// Wait for the load event ?
'if (document.readyState == "complete") {\n' +
'onLoad()\n' +
'} else {\n' +
'unsafeWindow.addEventListener("load", onLoad, false);\n' +
'}\n',
onMessage: function(msg) {
// test against doc caching
assert.equal(msg.someCount, 1, 'there is exactly one <setting> node for somePreference');
open(self).then(({ tabId }) => {
let tab = Tab({ tab: getTabForId(tabId) });
assert.pass('the add-on prefs page was opened.');
// test somePreference
assert.equal(msg.somePreference.type, 'string', 'some pref is a string');
assert.equal(msg.somePreference.pref, 'extensions.'+self.id+'.somePreference', 'somePreference path is correct');
assert.equal(msg.somePreference.title, 'some-title', 'somePreference title is correct');
assert.equal(msg.somePreference.desc, 'Some short description for the preference', 'somePreference description is correct');
assert.equal(msg.somePreference['data-jetpack-id'], self.id, 'data-jetpack-id attribute value is correct');
tab.attach({
contentScriptWhen: "end",
contentScript: 'self.postMessage({\n' +
'someCount: unsafeWindow.document.querySelectorAll("setting[title=\'some-title\']").length,\n' +
'somePreference: getAttributes(unsafeWindow.document.querySelector("setting[title=\'some-title\']")),\n' +
'myInteger: getAttributes(unsafeWindow.document.querySelector("setting[title=\'my-int\']")),\n' +
'myHiddenInt: getAttributes(unsafeWindow.document.querySelector("setting[title=\'hidden-int\']")),\n' +
'sayHello: getAttributes(unsafeWindow.document.querySelector("button[label=\'Click me!\']"))\n' +
'});\n' +
'function getAttributes(ele) {\n' +
'if (!ele) return {};\n' +
'return {\n' +
'pref: ele.getAttribute("pref"),\n' +
'type: ele.getAttribute("type"),\n' +
'title: ele.getAttribute("title"),\n' +
'desc: ele.getAttribute("desc"),\n' +
'"data-jetpack-id": ele.getAttribute(\'data-jetpack-id\')\n' +
'}\n' +
'}\n',
onMessage: msg => {
// test against doc caching
assert.equal(msg.someCount, 1, 'there is exactly one <setting> node for somePreference');
// test myInteger
assert.equal(msg.myInteger.type, 'integer', 'myInteger is a int');
assert.equal(msg.myInteger.pref, 'extensions.'+self.id+'.myInteger', 'extensions.test-simple-prefs.myInteger');
assert.equal(msg.myInteger.title, 'my-int', 'myInteger title is correct');
assert.equal(msg.myInteger.desc, 'How many of them we have.', 'myInteger desc is correct');
assert.equal(msg.myInteger['data-jetpack-id'], self.id, 'data-jetpack-id attribute value is correct');
// test somePreference
assert.equal(msg.somePreference.type, 'string', 'some pref is a string');
assert.equal(msg.somePreference.pref, 'extensions.'+self.id+'.somePreference', 'somePreference path is correct');
assert.equal(msg.somePreference.title, 'some-title', 'somePreference title is correct');
assert.equal(msg.somePreference.desc, 'Some short description for the preference', 'somePreference description is correct');
assert.equal(msg.somePreference['data-jetpack-id'], self.id, 'data-jetpack-id attribute value is correct');
// test myHiddenInt
assert.equal(msg.myHiddenInt.type, undefined, 'myHiddenInt was not displayed');
assert.equal(msg.myHiddenInt.pref, undefined, 'myHiddenInt was not displayed');
assert.equal(msg.myHiddenInt.title, undefined, 'myHiddenInt was not displayed');
assert.equal(msg.myHiddenInt.desc, undefined, 'myHiddenInt was not displayed');
// test myInteger
assert.equal(msg.myInteger.type, 'integer', 'myInteger is a int');
assert.equal(msg.myInteger.pref, 'extensions.'+self.id+'.myInteger', 'extensions.test-simple-prefs.myInteger');
assert.equal(msg.myInteger.title, 'my-int', 'myInteger title is correct');
assert.equal(msg.myInteger.desc, 'How many of them we have.', 'myInteger desc is correct');
assert.equal(msg.myInteger['data-jetpack-id'], self.id, 'data-jetpack-id attribute value is correct');
// test sayHello
assert.equal(msg.sayHello['data-jetpack-id'], self.id, 'data-jetpack-id attribute value is correct');
// test myHiddenInt
assert.equal(msg.myHiddenInt.type, undefined, 'myHiddenInt was not displayed');
assert.equal(msg.myHiddenInt.pref, undefined, 'myHiddenInt was not displayed');
assert.equal(msg.myHiddenInt.title, undefined, 'myHiddenInt was not displayed');
assert.equal(msg.myHiddenInt.desc, undefined, 'myHiddenInt was not displayed');
tab.close(done);
}
});
}
// test sayHello
assert.equal(msg.sayHello['data-jetpack-id'], self.id, 'data-jetpack-id attribute value is correct');
tab.close(done);
}
});
})
}
// run it again, to test against inline options document caching

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/. */
'use strict';
function install(data, reason) {}
function uninstall(data, reason) {}
function startup(data, reasonCode) {};
function shutdown(data, reasonCode) {};

View File

@ -0,0 +1,27 @@
<?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/. -->
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test-bootstrap-addon@mozilla.com</em:id>
<em:name>Test Bootstrap Add-on</em:name>
<em:creator>Erik Vold</em:creator>
<em:optionsType>2</em:optionsType>
<em:version>1.0</em:version>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<em:unpack>false</em:unpack>
<!-- Firefox -->
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>26.0</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View File

@ -0,0 +1,3 @@
<?xml version="1.0"?>
<!DOCTYPE mydialog SYSTEM "chrome://myaddon/locale/mydialog.dtd">
<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"></vbox>

View File

@ -1,5 +1,5 @@
{
"id": "jid1-fZHqN9JfrDBa8A",
"id": "no-prefs@jetpack",
"title": "No Prefs Test",
"author": "Erik Vold",
"loader": "lib/main.js"

View File

@ -1,5 +1,5 @@
{
"id": "jid1-fZHqN9JfrDBa8A",
"id": "simple-prefs@jetpack",
"title": "Simple Prefs Test",
"author": "Erik Vold",
"preferences": [{

View File

@ -9,7 +9,6 @@ const { on, off } = require("sdk/system/events");
const { setTimeout } = require("sdk/timers");
const tmp = require("sdk/test/tmp-file");
const system = require("sdk/system");
const fixtures = require("./fixtures");
const testFolderURL = module.uri.split('test-addon-installer.js')[0];
const ADDON_URL = testFolderURL + "fixtures/addon-install-unit-test@mozilla.com.xpi";

View File

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { setDefaults, injectOptions, validate } = require('sdk/preferences/native-options');
const { setDefaults, injectOptions: inject, validate } = require('sdk/preferences/native-options');
const { activeBrowserWindow: { document } } = require("sdk/deprecated/window-utils");
const { preferencesBranch, id } = require('sdk/self');
const { get } = require('sdk/preferences/service');
@ -12,6 +12,19 @@ const simple = require('sdk/simple-prefs');
const fixtures = require('./fixtures');
const { Cc, Ci } = require('chrome');
const prefsrv = Cc['@mozilla.org/preferences-service;1'].
getService(Ci.nsIPrefService);
function injectOptions(preferences, preferencesBranch, document, parent) {
inject({
id: id,
preferences: preferences,
preferencesBranch: preferencesBranch,
document: document,
parent: parent
});
}
exports.testValidate = function(assert) {
let { preferences } = packageJSON('simple-prefs');
@ -56,35 +69,45 @@ exports.testNoPrefs = function(assert, done) {
exports.testCurlyID = function(assert) {
let { preferences, id } = packageJSON('curly-id');
let branch = prefsrv.getDefaultBranch('extensions.' + id);
let parent = document.createDocumentFragment();
injectOptions(preferences, id, document, parent);
assert.equal(parent.children.length, 1, "One setting elements injected");
assert.equal(parent.firstElementChild.attributes.pref.value,
assert.equal(parent.firstElementChild.attributes.pref.value,
"extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test13",
"Setting pref attribute is set properly");
setDefaults(preferences, id);
assert.equal(get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test13'),
assert.equal(get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test13'),
26, "test13 is 26");
branch.deleteBranch('');
assert.equal(get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test13'),
undefined, "test13 is undefined");
}
exports.testPreferencesBranch = function(assert) {
let { preferences, 'preferences-branch': prefsBranch } = packageJSON('preferences-branch');
let branch = prefsrv.getDefaultBranch('extensions.' + prefsBranch);
let parent = document.createDocumentFragment();
injectOptions(preferences, prefsBranch, document, parent);
assert.equal(parent.children.length, 1, "One setting elements injected");
assert.equal(parent.firstElementChild.attributes.pref.value,
assert.equal(parent.firstElementChild.attributes.pref.value,
"extensions.human-readable.test42",
"Setting pref attribute is set properly");
setDefaults(preferences, prefsBranch);
assert.equal(get('extensions.human-readable.test42'), true, "test42 is true");
branch.deleteBranch('');
assert.equal(get('extensions.human-readable.test42'), undefined, "test42 is undefined");
}
exports.testSimplePrefs = function(assert) {
let { preferences } = packageJSON('simple-prefs');
let branch = prefsrv.getDefaultBranch('extensions.' + preferencesBranch);
function assertPref(setting, name, type, title) {
assert.equal(setting.getAttribute('data-jetpack-id'), id,
@ -128,13 +151,15 @@ exports.testSimplePrefs = function(assert) {
setDefaults(preferences, preferencesBranch);
assert.strictEqual(simple.prefs.test, false, "test is false");
assert.strictEqual(simple.prefs.test2, "\u00FCnic\u00F8d\u00E9", "test2 is unicode");
assert.strictEqual(simple.prefs.test2, "\u00FCnic\u00F8d\u00E9", "test2 is unicode");
assert.strictEqual(simple.prefs.test3, "1", "test3 is '1'");
assert.strictEqual(simple.prefs.test4, "red", "test4 is 'red'");
// default pref branch can't be "reset", bug 1012231
Cc['@mozilla.org/preferences-service;1'].getService(Ci.nsIPrefService).
getDefaultBranch('extensions.' + preferencesBranch).deleteBranch('');
branch.deleteBranch('');
assert.strictEqual(simple.prefs.test, undefined, "test is undefined");
assert.strictEqual(simple.prefs.test2, undefined, "test2 is undefined");
assert.strictEqual(simple.prefs.test3, undefined, "test3 is undefined");
assert.strictEqual(simple.prefs.test4, undefined, "test4 is undefined");
}
function packageJSON(dir) {

View File

@ -3,12 +3,29 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cc, Ci, Cu } = require('chrome');
const { Loader } = require("sdk/test/loader");
const { setTimeout } = require("sdk/timers");
const { emit } = require("sdk/system/events");
const { id } = require("sdk/self");
const simplePrefs = require("sdk/simple-prefs");
const { prefs: sp } = simplePrefs;
const { defer, resolve, reject, all } = require("sdk/core/promise");
const AddonInstaller = require("sdk/addon/installer");
const fixtures = require("./fixtures");
const { pathFor } = require("sdk/system");
const file = require("sdk/io/file");
const { install, uninstall } = require("sdk/addon/installer");
const { open } = require('sdk/preferences/utils');
const { toFilename } = require('sdk/url');
const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
const { ZipWriter } = require('./zip/utils');
const { getTabForId } = require('sdk/tabs/utils');
const { preferencesBranch, id } = require('sdk/self');
const { Tab } = require('sdk/tabs/tab');
require('sdk/tabs');
const prefsrv = Cc['@mozilla.org/preferences-service;1'].
getService(Ci.nsIPrefService);
const specialChars = "!@#$%^&*()_-=+[]{}~`\'\"<>,./?;:";
@ -230,4 +247,104 @@ exports.testPrefJSONStringification = function(assert) {
"JSON stringification should work.");
};
require('sdk/test').run(exports);
exports.testUnloadOfDynamicPrefGeneration = function(assert, done) {
let loader = Loader(module);
let branch = prefsrv.getDefaultBranch('extensions.' + preferencesBranch);
let { enable } = loader.require("sdk/preferences/native-options");
let addon_id = "test-bootstrap-addon@mozilla.com";
let xpi_path = file.join(pathFor("ProfD"), addon_id + ".xpi");
// zip the add-on
let zip = new ZipWriter(xpi_path);
assert.pass("start creating the xpi");
zip.addFile("", toFilename(fixtures.url("bootstrap-addon/"))).
then(zip.close()).
then(_ => install(xpi_path)).
// get the addon
then(id => {
let { promise, resolve } = defer();
AddonManager.getAddonByID(id, resolve);
return promise;
}).
// insatll the add-on
then(addon => {
assert.pass('installed');
assert.pass('addon id: ' + addon.id);
addon.userDisabled = false;
assert.ok(!addon.userDisabled, 'the add-on is enabled');
assert.ok(addon.isActive, 'the add-on is enabled');
// setup dynamic prefs
return enable({
id: addon.id,
preferences: [{
"name": "test",
"description": "test",
"title": "Test",
"type": "string",
"value": "default"
}, {
"name": "test-int",
"description": "test",
"type": "integer",
"value": 5,
"title": "How Many?"
}]
});
}).
then(args => {
assert.pass('enabled');
return args;
}).
// show inline prefs
then(open).
then(args => {
assert.pass('opened');
return args;
}).
// confirm dynamic pref generation did occur
then(args => {
let results = args.document.querySelectorAll("*[data-jetpack-id=\"" +args.id + "\"]");
assert.ok(results.length > 0, "the prefs were setup");
return args;
}).
// unload dynamic prefs
then(args => {
loader.unload();
assert.pass('unload');
return args;
}).
// hide and show the inline prefs
then(({ tabId, id, document }) => {
let { promise, resolve } = defer();
let tab = Tab({ tab: getTabForId(tabId) });
tab.close(_ => resolve({ id: id }));
return promise;
}).
// reopen the add-on prefs page
then(open).
// confirm dynamic pref generation did not occur
then(({ id, tabId, document }) => {
let { promise, resolve } = defer();
let tab = Tab({ tab: getTabForId(tabId) });
let results = document.querySelectorAll("*[data-jetpack-id=\"" + id + "\"]");
assert.equal(0, results.length, "the prefs were not setup after unload");
tab.close(_ => resolve({ id: id }));
return promise;
}).
// uninstall the add-on
then(({ id }) => uninstall(id)).
// delete the pref branch
then(_ => branch.deleteBranch('')).
then(done, assert.fail);
}
require("sdk/test").run(exports);

View File

@ -1089,41 +1089,39 @@ exports.testSidebarLeakCheckUnloadAfterAttach = function(assert, done) {
const loader = Loader(module);
const { Sidebar } = loader.require('sdk/ui/sidebar');
let testName = 'testSidebarLeakCheckUnloadAfterAttach';
let window = getMostRecentBrowserWindow();
let sidebar = Sidebar({
id: testName,
title: testName,
url: 'data:text/html;charset=utf-8,'+testName
});
sidebar.on('attach', function() {
assert.pass('the sidebar was shown');
open().then(focus).then(window => {
sidebar.on('attach', function() {
assert.pass('the sidebar was shown');
sidebar.on('show', function() {
assert.fail('the sidebar show listener should have been removed');
});
assert.pass('added a sidebar show listener');
sidebar.on('show', function() {
assert.fail('the sidebar show listener should have been removed');
});
assert.pass('added a sidebar show listener');
sidebar.on('hide', function() {
assert.fail('the sidebar hide listener should have been removed');
});
assert.pass('added a sidebar hide listener');
sidebar.on('hide', function() {
assert.fail('the sidebar hide listener should have been removed');
});
assert.pass('added a sidebar hide listener');
let panelBrowser = window.document.getElementById('sidebar').contentDocument.getElementById('web-panels-browser');
panelBrowser.contentWindow.addEventListener('unload', function onUnload() {
panelBrowser.contentWindow.removeEventListener('unload', onUnload, false);
// wait a tick..
setTimeout(function() {
let panelBrowser = window.document.getElementById('sidebar').contentDocument.getElementById('web-panels-browser');
panelBrowser.contentWindow.addEventListener('unload', function onUnload() {
panelBrowser.contentWindow.removeEventListener('unload', onUnload, false);
assert.pass('the sidebar web panel was unloaded properly');
done();
})
}, false);
close(window).then(done).catch(assert.fail);
}, false);
loader.unload();
});
loader.unload();
});
assert.pass('showing the sidebar');
sidebar.show();
assert.pass('showing the sidebar');
sidebar.show();
}).catch(assert.fail);
}
exports.testTwoSidebarsWithSameTitleAndURL = function(assert) {

View File

@ -0,0 +1,125 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Ci, Cc } = require("chrome");
const { defer, all } = require("sdk/core/promise");
const PR_RDONLY = 0x01;
const PR_WRONLY = 0x02;
const PR_RDWR = 0x04;
const PR_CREATE_FILE = 0x08;
const PR_APPEND = 0x10;
const PR_TRUNCATE = 0x20;
const PR_SYNC = 0x40;
const PR_EXCL = 0x80;
// Default compression level:
const { COMPRESSION_DEFAULT } = Ci.nsIZipWriter;
function createNsFile(path) {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
try {
file.initWithPath(path);
} catch(e) {
throw new Error("This zip file path is not valid : " + path + "\n" + e);
}
return file;
}
const converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
function streamForData(data) {
return converter.convertToInputStream(data);
}
exports.ZipWriter = function (zipPath, mode) {
mode = mode ? mode : PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE;
let zw = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
zw.open(createNsFile(zipPath), mode);
// Create a directory entry.
// Returns true if the entry was created, or false if the entry already exists
this.mkdir = function mkdir(pathInZip) {
try {
zw.addEntryDirectory(pathInZip, 0, false);
}
catch(e) {
if (e.name === "NS_ERROR_FILE_ALREADY_EXISTS")
return false;
throw e
}
return true;
}
this.addFile = function addFile(pathInZip, filePath) {
let { promise, reject, resolve } = defer();
let nsfile = createNsFile(filePath);
if (!nsfile.exists()) {
reject(new Error("This file doesn't exists : " + nsfile.path));
return promise;
}
// Case 1/ Regular file
if (!nsfile.isDirectory()) {
try {
zw.addEntryFile(pathInZip, COMPRESSION_DEFAULT, nsfile, false);
resolve();
}
catch (e) {
reject(new Error("Unable to add following file in zip: " + nsfile.path + "\n" + e));
}
return promise;
}
// Case 2/ Directory
if (pathInZip.substr(-1) !== '/')
pathInZip += "/";
let entries = nsfile.directoryEntries;
let array = [];
zw.addEntryDirectory(pathInZip, nsfile.lastModifiedTime, false);
while(entries.hasMoreElements()) {
let file = entries.getNext().QueryInterface(Ci.nsIFile);
if (file.leafName === "." || file.leafName === "..")
continue;
let path = pathInZip + file.leafName;
if (path.substr(0, 1) == '/') {
path = path.substr(1);
}
this.addFile(path, file.path);
}
resolve();
return promise;
}
this.addData = function (pathInZip, data) {
let deferred = defer();
try {
let stream = streamForData(data);
zw.addEntryStream(pathInZip, 0, COMPRESSION_DEFAULT, stream, false);
} catch(e) {
throw new Error("Unable to add following data in zip: " +
data.substr(0, 20) + "\n" + e);
}
deferred.resolve();
return deferred.promise;
}
this.close = function close() {
let deferred = defer();
zw.close();
deferred.resolve();
return deferred.promise;
}
}

View File

@ -1239,7 +1239,9 @@ var gBrowserInit = {
let mpEnabled = slot &&
slot.status != Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED &&
slot.status != Ci.nsIPKCS11Slot.SLOT_READY;
Services.telemetry.getHistogramById("MASTER_PASSWORD_ENABLED").add(mpEnabled);
if (mpEnabled) {
Services.telemetry.getHistogramById("MASTER_PASSWORD_ENABLED").add(mpEnabled);
}
}, 5000);
});
this.delayedStartupFinished = true;
@ -2090,10 +2092,23 @@ function BrowserViewSourceOfDocument(aDocument)
// view-source to access the cached copy of the content rather than
// refetching it from the network...
//
try{
var PageLoader = webNav.QueryInterface(Components.interfaces.nsIWebPageDescriptor);
try {
pageCookie = PageLoader.currentDescriptor;
#ifdef E10S_TESTING_ONLY
// Workaround for bug 988133, which causes a crash if we attempt to load
// the document from the cache when the document is a CPOW (which occurs
// if we're using remote tabs). This causes us to reload the document from
// the network in this case, so it's not a permanent solution, hence hiding
// it behind the E10S_TESTING_ONLY ifdef. This is just a band-aid fix until
// we can find something better - see bug 1025146.
if (!Cu.isCrossProcessWrapper(aDocument)) {
#endif
var PageLoader = webNav.QueryInterface(Components.interfaces.nsIWebPageDescriptor);
pageCookie = PageLoader.currentDescriptor;
#ifdef E10S_TESTING_ONLY
}
#endif
} catch(err) {
// If no page descriptor is available, just use the view-source URL...
}

View File

@ -29,6 +29,12 @@ add_task(function() {
let newWin = yield openAndLoadWindow({}, true);
info("Waiting for panel in new window to open");
let hideTrace = function() {
info(new Error().stack);
info("Panel was hidden.");
};
newWin.PanelUI.panel.addEventListener("popuphidden", hideTrace);
yield newWin.PanelUI.show();
let newWinBookmarksToolbarPlaceholder = newWin.document.getElementById(buttonId);
ok(newWinBookmarksToolbarPlaceholder.classList.contains("toolbarbutton-1"),
@ -36,6 +42,7 @@ add_task(function() {
is(newWinBookmarksToolbarPlaceholder.getAttribute("wrap"), "true",
"Button in new window should have 'wrap' attribute");
newWin.PanelUI.panel.removeEventListener("popuphidden", hideTrace);
//XXXgijs on Linux, we're sometimes seeing the panel being hidden early
// in the newly created window, probably because something else steals focus.
if (newWin.PanelUI.panel.state != "closed") {

View File

@ -15,6 +15,8 @@ const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
const EventEmitter = require("devtools/toolkit/event-emitter");
const { CallWatcherFront } = require("devtools/server/actors/call-watcher");
const { CanvasFront } = require("devtools/server/actors/canvas");
const Telemetry = require("devtools/shared/telemetry");
const telemetry = new Telemetry();
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
@ -119,6 +121,7 @@ let EventsHandler = {
* Listen for events emitted by the current tab target.
*/
initialize: function() {
telemetry.toolOpened("canvasdebugger");
this._onTabNavigated = this._onTabNavigated.bind(this);
gTarget.on("will-navigate", this._onTabNavigated);
gTarget.on("navigate", this._onTabNavigated);
@ -128,6 +131,7 @@ let EventsHandler = {
* Remove events emitted by the current tab target.
*/
destroy: function() {
telemetry.toolClosed("canvasdebugger");
gTarget.off("will-navigate", this._onTabNavigated);
gTarget.off("navigate", this._onTabNavigated);
},

View File

@ -20,36 +20,74 @@ let test = asyncTest(function*() {
let usage = yield csscoverage.getUsage(options.target);
yield usage.oneshot();
yield navigate(usage, options);
yield checkPages(usage);
yield checkEditorReport(usage);
yield checkPageReport(usage);
yield helpers.closeToolbar(options);
yield helpers.closeTab(options);
});
/**
* Just check current page
*/
function* navigate(usage, options) {
let running = yield usage._testOnly_isRunning();
ok(!running, "csscoverage not is running");
yield usage.oneshot();
running = yield usage._testOnly_isRunning();
ok(!running, "csscoverage not is running");
}
/**
* Check the expected pages have been visited
*/
function* checkPages(usage) {
let expectedVisited = [ PAGE_3 ];
let actualVisited = yield usage._testOnly_visitedPages();
isEqualJson(actualVisited, expectedVisited, 'Visited');
}
/**
* Check that createEditorReport returns the expected JSON
*/
function* checkEditorReport(usage) {
// Page1
let expectedPage1 = { reports: [] };
let actualPage1 = yield usage.createEditorReport(PAGE_1);
let actualPage1 = yield usage.createEditorReport(PAGE_1 + " \u2192 <style> index 0");
isEqualJson(actualPage1, expectedPage1, 'Page1');
// Page2
let expectedPage2 = { reports: [] };
let actualPage2 = yield usage.createEditorReport(PAGE_2);
let actualPage2 = yield usage.createEditorReport(PAGE_2 + " \u2192 <style> index 0");
isEqualJson(actualPage2, expectedPage2, 'Page2');
// Page3
let expectedPage3 = {
// Page3a
let expectedPage3a = {
reports: [
{
selectorText: ".page3-test2",
start: { line: 9, column: 5 },
},
}
]
};
let actualPage3a = yield usage.createEditorReport(PAGE_3 + " \u2192 <style> index 0");
isEqualJson(actualPage3a, expectedPage3a, 'Page3a');
// Page3b
let expectedPage3b = {
reports: [
{
selectorText: ".page3-test3",
start: { line: 3, column: 5 },
}
]
};
let actualPage3 = yield usage.createEditorReport(PAGE_3);
isEqualJson(actualPage3, expectedPage3, 'Page3');
let actualPage3b = yield usage.createEditorReport(PAGE_3 + " \u2192 <style> index 1");
isEqualJson(actualPage3b, expectedPage3b, 'Page3b');
// SheetA
let expectedSheetA = {
@ -130,11 +168,148 @@ let test = asyncTest(function*() {
};
let actualSheetD = yield usage.createEditorReport(SHEET_D);
isEqualJson(actualSheetD, expectedSheetD, 'SheetD');
}
yield helpers.closeToolbar(options);
yield helpers.closeTab(options);
});
/**
* Check that checkPageReport returns the expected JSON
*/
function* checkPageReport(usage) {
let actualReport = yield usage.createPageReport();
// Quick check on trivial things. See doc comment for checkRuleProperties
actualReport.preload.forEach(page => page.rules.forEach(checkRuleProperties));
actualReport.unused.forEach(page => page.rules.forEach(checkRuleProperties));
// Check the summary
let expectedSummary = { "used": 23, "unused": 9, "preload": 0 };
isEqualJson(actualReport.summary, expectedSummary, 'summary');
// Check the preload header
isEqualJson(actualReport.preload.length, 0, 'preload length');
// Check the unused header
isEqualJson(actualReport.unused.length, 6, 'unused length');
// Check the unused rules
isEqualJson(actualReport.unused[0].url, PAGE_3 + " \u2192 <style> index 0", "unused url 0");
let expectedUnusedRules0 = [
{
"url": PAGE_3 + " \u2192 <style> index 0",
"start": { "line": 9, "column": 5 },
"selectorText": ".page3-test2"
}
];
isEqualJson(actualReport.unused[0].rules, expectedUnusedRules0, 'unused rules 0');
isEqualJson(actualReport.unused[1].url, PAGE_3 + " \u2192 <style> index 1", "unused url 1");
let expectedUnusedRules1 = [
{
"url": PAGE_3 + " \u2192 <style> index 1",
"start": { "line": 3, "column": 5 },
"selectorText": ".page3-test3"
}
];
isEqualJson(actualReport.unused[1].rules, expectedUnusedRules1, 'unused rules 1');
isEqualJson(actualReport.unused[2].url, SHEET_A, "unused url 2");
let expectedUnusedRules2 = [
{
"url": SHEET_A,
"start": { "line": 8, "column": 1 },
"selectorText": ".sheetA-test2"
},
{
"url": SHEET_A,
"start": { "line": 12, "column": 1 },
"selectorText": ".sheetA-test3"
},
{
"url": SHEET_A,
"start": { "line": 16, "column": 1 },
"selectorText": ".sheetA-test4"
}
];
isEqualJson(actualReport.unused[2].rules, expectedUnusedRules2, 'unused rules 2');
isEqualJson(actualReport.unused[3].url, SHEET_B, "unused url 3");
let expectedUnusedRules3 = [
{
"url": SHEET_B,
"start": { "line": 6, "column": 1 },
"selectorText": ".sheetB-test2"
},
{
"url": SHEET_B,
"start": { "line": 10, "column": 1 },
"selectorText": ".sheetB-test3"
},
{
"url": SHEET_B,
"start": { "line": 14, "column": 1 },
"selectorText": ".sheetB-test4"
}
];
isEqualJson(actualReport.unused[3].rules, expectedUnusedRules3, 'unused rules 3');
isEqualJson(actualReport.unused[4].url, SHEET_D, "unused url 4");
let expectedUnusedRules4 = [
{
"url": SHEET_D,
"start": { "line": 6, "column": 1 },
"selectorText": ".sheetD-test2"
},
{
"url": SHEET_D,
"start": { "line": 10, "column": 1 },
"selectorText": ".sheetD-test3"
},
{
"url": SHEET_D,
"start": { "line": 14, "column": 1 },
"selectorText": ".sheetD-test4"
}
];
isEqualJson(actualReport.unused[4].rules, expectedUnusedRules4, 'unused rules 4');
isEqualJson(actualReport.unused[5].url, SHEET_C, "unused url 5");
let expectedUnusedRules5 = [
{
"url": SHEET_C,
"start": { "line": 6, "column": 1 },
"selectorText": ".sheetC-test2"
},
{
"url": SHEET_C,
"start": { "line": 10, "column": 1 },
"selectorText": ".sheetC-test3"
},
{
"url": SHEET_C,
"start": { "line": 14, "column": 1 },
"selectorText": ".sheetC-test4"
}
];
isEqualJson(actualReport.unused[5].rules, expectedUnusedRules5, 'unused rules 5');
}
/**
* We do basic tests on the shortUrl and formattedCssText because they are
* very derivative, and so make for fragile tests, and having done those quick
* existence checks we remove them so the JSON check later can ignore them
*/
function checkRuleProperties(rule, index) {
is(typeof rule.shortUrl, "string", "typeof rule.shortUrl for " + index);
is(rule.shortUrl.indexOf("http://"), -1, "http not in rule.shortUrl for" + index);
delete rule.shortUrl;
is(typeof rule.formattedCssText, "string", "typeof rule.formattedCssText for " + index);
ok(rule.formattedCssText.indexOf("{") > 0, "{ in rule.formattedCssText for " + index);
delete rule.formattedCssText;
}
/**
* Utility to compare JSON structures
*/
function isEqualJson(o1, o2, msg) {
is(JSON.stringify(o1), JSON.stringify(o2), msg);
}

View File

@ -42,11 +42,13 @@
/* How quickly do we rush through this? */
let delay = 500;
window.addEventListener("load", () => {
dump('TEST-INFO | load from browser_cmd_csscoverage_page1.html\n');
setTimeout(() => {
dump('TEST-INFO | timeout from browser_cmd_csscoverage_page1.html\n');
/* This adds <div class=page1-test5></div> */
let parent = document.querySelector("#page1-test5-holder");
let child = document.createElement("div");
child.classList.add("class=page1-test5");
child.classList.add("page1-test5");
parent.appendChild(child);
/* Then navigate to the next step */

View File

@ -26,11 +26,13 @@
/* How quickly do we rush through this? */
let delay = 500;
window.addEventListener("load", () => {
dump('TEST-INFO | load from browser_cmd_csscoverage_page2.html\n');
setTimeout(() => {
dump('TEST-INFO | timeout from browser_cmd_csscoverage_page2.html\n');
/* This adds <div class=page2-test3></div> */
let parent = document.querySelector("#page2-test3-holder");
let child = document.createElement("div");
child.classList.add("class=page2-test3");
child.classList.add("page2-test3");
parent.appendChild(child);
}, delay);
});
@ -41,7 +43,6 @@
<h2>Page 2</h2>
<div class=page2-test1>.page2-test1</div>
<div class=page2-test3>.page2-test3</div>
<div id=page2-test3-holder></div>

View File

@ -23,6 +23,11 @@
</style>
<link rel="stylesheet" type="text/css" href="browser_cmd_csscoverage_sheetA.css">
<link rel="stylesheet" type="text/css" href="browser_cmd_csscoverage_sheetB.css">
<script type="application/javascript;version=1.8">
window.addEventListener("load", () => {
dump('TEST-INFO | load from browser_cmd_csscoverage_page3.html\n');
});
</script>
</head>
<body>

View File

@ -20,43 +20,100 @@ let test = asyncTest(function*() {
let usage = yield csscoverage.getUsage(options.target);
yield navigate(usage, options);
yield checkPages(usage);
yield checkEditorReport(usage);
yield checkPageReport(usage);
yield helpers.closeToolbar(options);
yield helpers.closeTab(options);
});
/**
* Visit all the pages in the test
*/
function* navigate(usage, options) {
yield usage.start();
let running = yield usage._testOnly_isRunning();
ok(running, "csscoverage is running");
yield helpers.navigate(PAGE_3, options);
yield helpers.navigate(PAGE_1, options);
// Wait for the test pages to auto-cycle
let ev = yield helpers.listenOnce(options.browser, "load", true);
is(ev.target.location.href, PAGE_1, "page 1 loaded");
ev = yield helpers.listenOnce(options.browser, "load", true);
is(ev.target.location.href, PAGE_3, "page 3 loaded");
yield usage.stop();
running = yield usage._testOnly_isRunning();
ok(!running, "csscoverage not is running");
}
/**
* Check the expected pages have been visited
*/
function* checkPages(usage) {
// 'load' event order. '' is for the initial location
let expectedVisited = [ '', PAGE_2, PAGE_1, PAGE_3 ];
let actualVisited = yield usage._testOnly_visitedPages();
isEqualJson(actualVisited, expectedVisited, 'Visited');
}
/**
* Check that createEditorReport returns the expected JSON
*/
function* checkEditorReport(usage) {
// Page1
let expectedPage1 = { reports: [] };
let actualPage1 = yield usage.createEditorReport(PAGE_1);
let expectedPage1 = {
reports: [
{
selectorText: ".page1-test2",
start: { line: 8, column: 5 },
}
]
};
let actualPage1 = yield usage.createEditorReport(PAGE_1 + " \u2192 <style> index 0");
isEqualJson(actualPage1, expectedPage1, 'Page1');
// Page2
let expectedPage2 = { reports: [] };
let actualPage2 = yield usage.createEditorReport(PAGE_2);
let expectedPage2 = {
reports: [
{
selectorText: ".page2-test2",
start: { line: 9, column: 5 },
},
]
};
let actualPage2 = yield usage.createEditorReport(PAGE_2 + " \u2192 <style> index 0");
isEqualJson(actualPage2, expectedPage2, 'Page2');
// Page3
let expectedPage3 = {
// Page3a
let expectedPage3a = {
reports: [
{
selectorText: ".page3-test2",
start: { line: 9, column: 5 },
},
}
]
};
let actualPage3a = yield usage.createEditorReport(PAGE_3 + " \u2192 <style> index 0");
isEqualJson(actualPage3a, expectedPage3a, 'Page3a');
// Page3b
let expectedPage3b = {
reports: [
{
selectorText: ".page3-test3",
start: { line: 3, column: 5 },
}
]
};
let actualPage3 = yield usage.createEditorReport(PAGE_3);
isEqualJson(actualPage3, expectedPage3, 'Page3');
let actualPage3b = yield usage.createEditorReport(PAGE_3 + " \u2192 <style> index 1");
isEqualJson(actualPage3b, expectedPage3b, 'Page3b');
// SheetA
let expectedSheetA = {
@ -64,14 +121,6 @@ let test = asyncTest(function*() {
{
selectorText: ".sheetA-test2",
start: { line: 8, column: 1 },
},
{
selectorText: ".sheetA-test3",
start: { line: 12, column: 1 },
},
{
selectorText: ".sheetA-test4",
start: { line: 16, column: 1 },
}
]
};
@ -84,14 +133,6 @@ let test = asyncTest(function*() {
{
selectorText: ".sheetB-test2",
start: { line: 6, column: 1 },
},
{
selectorText: ".sheetB-test3",
start: { line: 10, column: 1 },
},
{
selectorText: ".sheetB-test4",
start: { line: 14, column: 1 },
}
]
};
@ -104,14 +145,6 @@ let test = asyncTest(function*() {
{
selectorText: ".sheetC-test2",
start: { line: 6, column: 1 },
},
{
selectorText: ".sheetC-test3",
start: { line: 10, column: 1 },
},
{
selectorText: ".sheetC-test4",
start: { line: 14, column: 1 },
}
]
};
@ -124,24 +157,303 @@ let test = asyncTest(function*() {
{
selectorText: ".sheetD-test2",
start: { line: 6, column: 1 },
},
{
selectorText: ".sheetD-test3",
start: { line: 10, column: 1 },
},
{
selectorText: ".sheetD-test4",
start: { line: 14, column: 1 },
}
]
};
let actualSheetD = yield usage.createEditorReport(SHEET_D);
isEqualJson(actualSheetD, expectedSheetD, 'SheetD');
}
yield helpers.closeToolbar(options);
yield helpers.closeTab(options);
});
/**
* Check that checkPageReport returns the expected JSON
*/
function* checkPageReport(usage) {
let actualReport = yield usage.createPageReport();
// Quick check on trivial things. See doc comment for checkRuleProperties
actualReport.preload.forEach(page => page.rules.forEach(checkRuleProperties));
actualReport.unused.forEach(page => page.rules.forEach(checkRuleProperties));
// Check the summary
let expectedSummary = { "used": 92, "unused": 22, "preload": 28 };
isEqualJson(actualReport.summary, expectedSummary, 'summary');
checkPageReportPreload(actualReport);
checkPageReportUnused(actualReport);
}
/**
* Check that checkPageReport returns the expected preload JSON
*/
function checkPageReportPreload(actualReport) {
// Check the preload header
isEqualJson(actualReport.preload.length, 3, 'preload length');
// Check the preload rules
isEqualJson(actualReport.preload[0].url, PAGE_2, 'preload url 0');
let expectedPreloadRules0 = [
// TODO: This is already pre-loaded, we should note this
{
url: PAGE_2 + " \u2192 <style> index 0",
start: { line: 5, column: 5 },
selectorText: ".page2-test1"
},
{
url: SHEET_A,
start: { line: 4, column: 1 },
selectorText: ".sheetA-test1"
},
{
url: SHEET_A,
start: { line: 16, column: 1 },
selectorText: ".sheetA-test4"
},
{
url: SHEET_B,
start: { line: 2, column: 1 },
selectorText: ".sheetB-test1"
},
{
url: SHEET_B,
start: { line: 14, column: 1 },
selectorText: ".sheetB-test4"
},
{
url: SHEET_D,
start: { line: 2, column: 1 },
selectorText: ".sheetD-test1"
},
{
url: SHEET_D,
start: { line: 14, column: 1 },
selectorText: ".sheetD-test4"
},
{
url: SHEET_C,
start: { line: 2, column: 1 },
selectorText: ".sheetC-test1"
},
{
url: SHEET_C,
start: { line: 14, column: 1 },
selectorText: ".sheetC-test4"
}
];
isEqualJson(actualReport.preload[0].rules, expectedPreloadRules0, 'preload rules 0');
isEqualJson(actualReport.preload[1].url, PAGE_1, 'preload url 1');
let expectedPreloadRules1 = [
{
url: SHEET_A,
start: { line: 4, column: 1 },
selectorText: ".sheetA-test1"
},
{
url: SHEET_A,
start: { line: 12, column: 1 },
selectorText: ".sheetA-test3"
},
{
url: SHEET_B,
start: { line: 2, column: 1 },
selectorText: ".sheetB-test1"
},
{
url: SHEET_B,
start: { line: 10, column: 1 },
selectorText: ".sheetB-test3"
},
{
url: SHEET_D,
start: { line: 2, column: 1 },
selectorText: ".sheetD-test1"
},
{
url: SHEET_D,
start: { line: 10, column: 1 },
selectorText: ".sheetD-test3"
},
{
url: SHEET_C,
start: { line: 2, column: 1 },
selectorText: ".sheetC-test1"
},
{
url: SHEET_C,
start: { line: 10, column: 1 },
selectorText: ".sheetC-test3"
},
{
url: PAGE_1 + " \u2192 <style> index 0",
start: { line: 4, column: 5 },
selectorText: ".page1-test1"
},
{
url: PAGE_1 + " \u2192 <style> index 0",
start: { line: 12, column: 5 },
selectorText: ".page1-test3:hover"
}
];
isEqualJson(actualReport.preload[1].rules, expectedPreloadRules1, 'preload rules 1');
isEqualJson(actualReport.preload[2].url, PAGE_3, 'preload url 2');
let expectedPreloadRules2 = [
{
url: SHEET_A,
start: { line: 4, column: 1 },
selectorText: ".sheetA-test1"
},
{
url: SHEET_A,
start: { line: 20, column: 1 },
selectorText: ".sheetA-test5"
},
{
url: SHEET_B,
start: { line: 2, column: 1 },
selectorText: ".sheetB-test1"
},
{
url: SHEET_B,
start: { line: 18, column: 1 },
selectorText: ".sheetB-test5"
},
{
url: SHEET_D,
start: { line: 2, column: 1 },
selectorText: ".sheetD-test1"
},
{
url: SHEET_D,
start: { line: 18, column: 1 },
selectorText: ".sheetD-test5"
},
{
url: SHEET_C,
start: { line: 2, column: 1 },
selectorText: ".sheetC-test1"
},
{
url: SHEET_C,
start: { line: 18, column: 1 },
selectorText: ".sheetC-test5"
},
{
url: PAGE_3 + " \u2192 <style> index 0",
start: { line: 5, column: 5 },
selectorText: ".page3-test1"
},
];
isEqualJson(actualReport.preload[2].rules, expectedPreloadRules2, 'preload rules 2');
}
/**
* Check that checkPageReport returns the expected unused JSON
*/
function checkPageReportUnused(actualReport) {
// Check the unused header
isEqualJson(actualReport.unused.length, 8, 'unused length');
// Check the unused rules
isEqualJson(actualReport.unused[0].url, PAGE_2 + " \u2192 <style> index 0", "unused url 0");
let expectedUnusedRules0 = [
{
url: PAGE_2 + " \u2192 <style> index 0",
start: { line: 9, column: 5 },
selectorText: ".page2-test2"
}
];
isEqualJson(actualReport.unused[0].rules, expectedUnusedRules0, 'unused rules 0');
isEqualJson(actualReport.unused[1].url, SHEET_A, "unused url 1");
let expectedUnusedRules1 = [
{
url: SHEET_A,
start: { line: 8, column: 1 },
selectorText: ".sheetA-test2"
}
];
isEqualJson(actualReport.unused[1].rules, expectedUnusedRules1, 'unused rules 1');
isEqualJson(actualReport.unused[2].url, SHEET_B, "unused url 2");
let expectedUnusedRules2 = [
{
url: SHEET_B,
start: { line: 6, column: 1 },
selectorText: ".sheetB-test2"
}
];
isEqualJson(actualReport.unused[2].rules, expectedUnusedRules2, 'unused rules 2');
isEqualJson(actualReport.unused[3].url, SHEET_D, "unused url 3");
let expectedUnusedRules3 = [
{
url: SHEET_D,
start: { line: 6, column: 1 },
selectorText: ".sheetD-test2"
}
];
isEqualJson(actualReport.unused[3].rules, expectedUnusedRules3, 'unused rules 3');
isEqualJson(actualReport.unused[4].url, SHEET_C, "unused url 4");
let expectedUnusedRules4 = [
{
url: SHEET_C,
start: { line: 6, column: 1 },
selectorText: ".sheetC-test2"
}
];
isEqualJson(actualReport.unused[4].rules, expectedUnusedRules4, 'unused rules 4');
isEqualJson(actualReport.unused[5].url, PAGE_1 + " \u2192 <style> index 0", "unused url 5");
let expectedUnusedRules5 = [
{
url: PAGE_1 + " \u2192 <style> index 0",
start: { line: 8, column: 5 },
selectorText: ".page1-test2"
}
];
isEqualJson(actualReport.unused[5].rules, expectedUnusedRules5, 'unused rules 5');
isEqualJson(actualReport.unused[6].url, PAGE_3 + " \u2192 <style> index 0", "unused url 6");
let expectedUnusedRules6 = [
{
url: PAGE_3 + " \u2192 <style> index 0",
start: { line: 9, column: 5 },
selectorText: ".page3-test2"
}
];
isEqualJson(actualReport.unused[6].rules, expectedUnusedRules6, 'unused rules 6');
isEqualJson(actualReport.unused[7].url, PAGE_3 + " \u2192 <style> index 1", "unused url 7");
let expectedUnusedRules7 = [
{
url: PAGE_3 + " \u2192 <style> index 1",
start: { line: 3, column: 5 },
selectorText: ".page3-test3"
}
];
isEqualJson(actualReport.unused[7].rules, expectedUnusedRules7, 'unused rules 7');
}
/**
* We do basic tests on the shortUrl and formattedCssText because they are
* very derivative, and so make for fragile tests, and having done those quick
* existence checks we remove them so the JSON check later can ignore them
*/
function checkRuleProperties(rule, index) {
is(typeof rule.shortUrl, "string", "typeof rule.shortUrl for " + index);
is(rule.shortUrl.indexOf("http://"), -1, "http not in rule.shortUrl for" + index);
delete rule.shortUrl;
is(typeof rule.formattedCssText, "string", "typeof rule.formattedCssText for " + index);
ok(rule.formattedCssText.indexOf("{") > 0, "{ in rule.formattedCssText for " + index);
delete rule.formattedCssText;
}
/**
* Utility to compare JSON structures
*/
function isEqualJson(o1, o2, msg) {
is(JSON.stringify(o1), JSON.stringify(o2), msg);
}

View File

@ -489,9 +489,9 @@ helpers._actual = {
},
unassigned: function(options) {
return options.requisition._unassigned.map(function(assignment) {
return options.requisition._unassigned.map(assignment => {
return assignment.arg.toString();
}.bind(this));
});
},
outputState: function(options) {
@ -540,7 +540,7 @@ helpers._createDebugCheck = function(options) {
var hintsPromise = helpers._actual.hints(options);
var predictionsPromise = helpers._actual.predictions(options);
return Promise.all(hintsPromise, predictionsPromise).then(function(values) {
return Promise.all(hintsPromise, predictionsPromise).then((values) => {
var hints = values[0];
var predictions = values[1];
var output = '';
@ -610,7 +610,7 @@ helpers._createDebugCheck = function(options) {
output += ']);';
return output;
}.bind(this), util.errorHandler);
}, util.errorHandler);
};
/**
@ -933,7 +933,7 @@ helpers._exec = function(options, name, expected) {
}
try {
return requisition.exec({ hidden: true }).then(function(output) {
return requisition.exec({ hidden: true }).then((output) => {
if ('type' in expected) {
assert.is(output.type,
expected.type,
@ -993,7 +993,7 @@ helpers._exec = function(options, name, expected) {
}
return { output: output, text: textOutput };
});
}.bind(this)).then(function(data) {
}).then(function(data) {
if (expected.error) {
cli.logErrors = origLogErrors;
}

View File

@ -273,25 +273,25 @@ DevTools.prototype = {
this._toolboxes.set(target, toolbox);
toolbox.once("destroyed", function() {
toolbox.once("destroyed", () => {
this._toolboxes.delete(target);
this.emit("toolbox-destroyed", target);
}.bind(this));
});
// If we were asked for a specific tool then we need to wait for the
// tool to be ready, otherwise we can just wait for toolbox open
if (toolId != null) {
toolbox.once(toolId + "-ready", function(event, panel) {
toolbox.once(toolId + "-ready", (event, panel) => {
this.emit("toolbox-ready", toolbox);
deferred.resolve(toolbox);
}.bind(this));
});
toolbox.open();
}
else {
toolbox.open().then(function() {
toolbox.open().then(() => {
deferred.resolve(toolbox);
this.emit("toolbox-ready", toolbox);
}.bind(this));
});
}
}

View File

@ -71,14 +71,14 @@ ToolSidebar.prototype = {
let tab = this._tabbox.tabs.appendItem();
tab.setAttribute("label", ""); // Avoid showing "undefined" while the tab is loading
let onIFrameLoaded = function() {
let onIFrameLoaded = () => {
tab.setAttribute("label", iframe.contentDocument.title);
iframe.removeEventListener("load", onIFrameLoaded, true);
if ("setPanel" in iframe.contentWindow) {
iframe.contentWindow.setPanel(this._toolPanel, iframe);
}
this.emit(id + "-ready");
}.bind(this);
};
iframe.addEventListener("load", onIFrameLoaded, true);
@ -101,9 +101,9 @@ ToolSidebar.prototype = {
// For some reason I don't understand, if we call this.select in this
// event loop (after inserting the tab), the tab will never get the
// the "selected" attribute set to true.
this._panelDoc.defaultView.setTimeout(function() {
this._panelDoc.defaultView.setTimeout(() => {
this.select(id);
}.bind(this), 10);
}, 10);
}
this.emit("new-tab-registered", id);

View File

@ -365,7 +365,7 @@ TabTarget.prototype = {
};
this.client.addListener("tabDetached", this._onTabDetached);
this._onTabNavigated = function onRemoteTabNavigated(aType, aPacket) {
this._onTabNavigated = (aType, aPacket) => {
let event = Object.create(null);
event.url = aPacket.url;
event.title = aPacket.title;
@ -381,7 +381,7 @@ TabTarget.prototype = {
this.emit("navigate", event);
this._navWindow = null;
}
}.bind(this);
};
this.client.addListener("tabNavigated", this._onTabNavigated);
},

View File

@ -122,11 +122,11 @@ DevToolPanel.prototype = {
open: function() {
let deferred = promise.defer();
executeSoon(function() {
executeSoon(() => {
this._isReady = true;
this.emit("ready");
deferred.resolve(this);
}.bind(this));
});
return deferred.promise;
},

View File

@ -48,7 +48,7 @@ function selectAndCheckById(id) {
}
function testToggle() {
toolbox.once("destroyed", function() {
toolbox.once("destroyed", () => {
// Cannot reuse a target after it's destroyed.
target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, "styleeditor").then(function(aToolbox) {
@ -58,7 +58,7 @@ function testToggle() {
finishUp();
});
});
}.bind(this));
});
toolbox.destroy();
}

View File

@ -30,7 +30,7 @@ function test() {
isTargetSupported: function() true,
build: function(iframeWindow, toolbox) {
let deferred = promise.defer();
executeSoon(function() {
executeSoon(() => {
deferred.resolve({
target: toolbox.target,
toolbox: toolbox,
@ -38,7 +38,7 @@ function test() {
destroy: function(){},
panelDoc: iframeWindow.document,
});
}.bind(this));
});
return deferred.promise;
},
};

View File

@ -60,10 +60,10 @@ BottomHost.prototype = {
this._nbox.appendChild(this._splitter);
this._nbox.appendChild(this.frame);
let frameLoad = function() {
let frameLoad = () => {
this.emit("ready", this.frame);
deferred.resolve(this.frame);
}.bind(this);
};
this.frame.tooltip = "aHTMLTooltip";
@ -143,10 +143,10 @@ SidebarHost.prototype = {
this._sidebar.appendChild(this._splitter);
this._sidebar.appendChild(this.frame);
let frameLoad = function() {
let frameLoad = () => {
this.emit("ready", this.frame);
deferred.resolve(this.frame);
}.bind(this);
};
this.frame.tooltip = "aHTMLTooltip";
this.frame.setAttribute("src", "about:blank");
@ -213,14 +213,14 @@ WindowHost.prototype = {
let win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank",
flags, null);
let frameLoad = function(event) {
let frameLoad = (event) => {
win.removeEventListener("load", frameLoad, true);
win.focus();
this.frame = win.document.getElementById("toolbox-iframe");
this.emit("ready", this.frame);
deferred.resolve(this.frame);
}.bind(this);
};
win.addEventListener("load", frameLoad, true);
win.addEventListener("unload", this._boundUnload);

View File

@ -101,12 +101,12 @@ HTMLBreadcrumbs.prototype = {
this.container._scrollButtonUp.collapsed = true;
this.container._scrollButtonDown.collapsed = true;
this.onscrollboxreflow = function() {
this.onscrollboxreflow = () => {
if (this.container._scrollButtonDown.collapsed)
this.container.removeAttribute("overflows");
else
this.container.setAttribute("overflows", true);
}.bind(this);
};
this.container.addEventListener("underflow", this.onscrollboxreflow, false);
this.container.addEventListener("overflow", this.onscrollboxreflow, false);
@ -480,9 +480,9 @@ HTMLBreadcrumbs.prototype = {
button.click();
}
button.onBreadcrumbsClick = function onBreadcrumbsClick() {
button.onBreadcrumbsClick = () => {
this.selection.setNodeFront(aNode, "breadcrumbs");
}.bind(this);
};
button.onclick = (function _onBreadcrumbsRightClick(event) {
button.focus();

View File

@ -136,7 +136,7 @@ InspectorPanel.prototype = {
// Show a warning when the debugger is paused.
// We show the warning only when the inspector
// is selected.
this.updateDebuggerPausedWarning = function() {
this.updateDebuggerPausedWarning = () => {
let notificationBox = this._toolbox.getNotificationBox();
let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
if (!notification && this._toolbox.currentToolId == "inspector" &&
@ -154,7 +154,7 @@ InspectorPanel.prototype = {
notificationBox.removeNotification(notification);
}
}.bind(this);
};
this.target.on("thread-paused", this.updateDebuggerPausedWarning);
this.target.on("thread-resumed", this.updateDebuggerPausedWarning);
this._toolbox.on("select", this.updateDebuggerPausedWarning);
@ -164,7 +164,7 @@ InspectorPanel.prototype = {
this._initMarkup();
this.isReady = false;
this.once("markuploaded", function() {
this.once("markuploaded", () => {
this.isReady = true;
// All the components are initialized. Let's select a node.
@ -174,7 +174,7 @@ InspectorPanel.prototype = {
this.emit("ready");
deferred.resolve(this);
}.bind(this));
});
this.setupSearchBox();
this.setupSidebar();
@ -281,9 +281,9 @@ InspectorPanel.prototype = {
let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
this._setDefaultSidebar = function(event, toolId) {
this._setDefaultSidebar = (event, toolId) => {
Services.prefs.setCharPref("devtools.inspector.activeSidebar", toolId);
}.bind(this);
};
this.sidebar.on("select", this._setDefaultSidebar);
@ -850,10 +850,10 @@ InspectorPanel.prototype = {
if (this._timer) {
return null;
}
this._timer = this.panelWin.setTimeout(function() {
this._timer = this.panelWin.setTimeout(() => {
this.emit("layout-change");
this._timer = null;
}.bind(this), LAYOUT_CHANGE_TIMER);
}, LAYOUT_CHANGE_TIMER);
}
},

View File

@ -231,12 +231,12 @@ MarkupView.prototype = {
},
update: function() {
let updateChildren = function(node) {
let updateChildren = (node) => {
this.getContainer(node).update();
for (let child of node.treeChildren()) {
updateChildren(child);
}
}.bind(this);
};
// Start with the documentElement
let documentElement;
@ -361,11 +361,15 @@ MarkupView.prototype = {
switch(aEvent.keyCode) {
case Ci.nsIDOMKeyEvent.DOM_VK_H:
let node = this._selectedContainer.node;
if (node.hidden) {
this.walker.unhideNode(node).then(() => this.nodeChanged(node));
if (aEvent.metaKey || aEvent.shiftKey) {
handled = false;
} else {
this.walker.hideNode(node).then(() => this.nodeChanged(node));
let node = this._selectedContainer.node;
if (node.hidden) {
this.walker.unhideNode(node).then(() => this.nodeChanged(node));
} else {
this.walker.hideNode(node).then(() => this.nodeChanged(node));
}
}
break;
case Ci.nsIDOMKeyEvent.DOM_VK_DELETE:
@ -1240,10 +1244,10 @@ MarkupView.prototype = {
this._previewBar.classList.add("hide");
win.clearTimeout(this._resizePreviewTimeout);
win.setTimeout(function() {
win.setTimeout(() => {
this._updatePreview();
this._previewBar.classList.remove("hide");
}.bind(this), 1000);
}, 1000);
}
};

View File

@ -52,7 +52,7 @@ function Cleopatra(panel, opts) {
// or when user clicks on start/stop buttons.
doc.getElementById("profiler-report").appendChild(this.iframe);
win.addEventListener("message", function (event) {
win.addEventListener("message", (event) => {
if (parseInt(event.data.uid, 10) !== parseInt(this.uid, 10)) {
return;
}
@ -65,7 +65,7 @@ function Cleopatra(panel, opts) {
case "displaysource":
this.panel.displaySource(event.data.data);
}
}.bind(this));
});
}
Cleopatra.prototype = {
@ -163,4 +163,5 @@ Cleopatra.prototype = {
}
};
module.exports = Cleopatra;
module.exports = Cleopatra;

View File

@ -18,6 +18,35 @@ var AppManagerRenderer = Class({
return AppProjectEditor;
}
},
getUI: function(parent) {
let doc = parent.ownerDocument;
if (parent.childElementCount == 0) {
let image = doc.createElement("image");
let optionImage = doc.createElement("image");
let flexElement = doc.createElement("div");
let nameLabel = doc.createElement("span");
let statusElement = doc.createElement("div");
image.className = "project-image";
optionImage.className = "project-options";
optionImage.setAttribute("src", OPTION_URL);
nameLabel.className = "project-name-label";
statusElement.className = "project-status";
flexElement.className = "project-flex";
parent.appendChild(image);
parent.appendChild(nameLabel);
parent.appendChild(flexElement);
parent.appendChild(statusElement);
parent.appendChild(optionImage);
}
return {
image: parent.querySelector(".project-image"),
nameLabel: parent.querySelector(".project-name-label"),
statusElement: parent.querySelector(".project-status")
};
},
onAnnotate: function(resource, editor, elt) {
if (resource.parent || !this.isAppManagerProject()) {
return;
@ -25,33 +54,16 @@ var AppManagerRenderer = Class({
let {appManagerOpts} = this.host.project;
let doc = elt.ownerDocument;
let image = doc.createElement("image");
let optionImage = doc.createElement("image");
let flexElement = doc.createElement("div");
let nameLabel = doc.createElement("span");
let statusElement = doc.createElement("div");
image.className = "project-image";
optionImage.className = "project-options";
nameLabel.className = "project-name-label";
statusElement.className = "project-status";
flexElement.className = "project-flex";
let {image,nameLabel,statusElement} = this.getUI(elt);
let name = appManagerOpts.name || resource.basename;
let url = appManagerOpts.iconUrl || "icon-sample.png";
let status = appManagerOpts.validationStatus || "unknown";
nameLabel.textContent = name;
image.setAttribute("src", url);
optionImage.setAttribute("src", OPTION_URL);
statusElement.setAttribute("status", status)
statusElement.setAttribute("status", status);
elt.innerHTML = "";
elt.appendChild(image);
elt.appendChild(nameLabel);
elt.appendChild(flexElement);
elt.appendChild(statusElement);
elt.appendChild(optionImage);
return true;
}
});

View File

@ -47,12 +47,7 @@ let test = asyncTest(function*() {
validationStatus: "error"
});
ok (!nameLabel.parentNode, "The old elements have been removed");
info ("Getting ahold of and validating the project header DOM");
let image = header.querySelector(".project-image");
let nameLabel = header.querySelector(".project-name-label");
let statusElement = header.querySelector(".project-status");
is (statusElement.getAttribute("status"), "error", "The status has been set correctly.");
is (nameLabel.textContent, "Test2", "The name label has been set correctly");
is (image.getAttribute("src"), "chrome://browser/skin/devtools/tool-inspector.svg", "The icon has been set correctly");
@ -65,12 +60,7 @@ let test = asyncTest(function*() {
validationStatus: "warning"
});
ok (!nameLabel.parentNode, "The old elements have been removed");
info ("Getting ahold of and validating the project header DOM");
let image = header.querySelector(".project-image");
let nameLabel = header.querySelector(".project-name-label");
let statusElement = header.querySelector(".project-status");
is (statusElement.getAttribute("status"), "warning", "The status has been set correctly.");
is (nameLabel.textContent, "Test3", "The name label has been set correctly");
is (image.getAttribute("src"), "chrome://browser/skin/devtools/tool-webconsole.svg", "The icon has been set correctly");
@ -83,12 +73,7 @@ let test = asyncTest(function*() {
validationStatus: "valid"
});
ok (!nameLabel.parentNode, "The old elements have been removed");
info ("Getting ahold of and validating the project header DOM");
let image = header.querySelector(".project-image");
let nameLabel = header.querySelector(".project-name-label");
let statusElement = header.querySelector(".project-status");
is (statusElement.getAttribute("status"), "valid", "The status has been set correctly.");
is (nameLabel.textContent, "Test4", "The name label has been set correctly");
is (image.getAttribute("src"), "chrome://browser/skin/devtools/tool-debugger.svg", "The icon has been set correctly");

View File

@ -36,7 +36,7 @@ this.SplitView = function SplitView(aRoot)
this._mql = aRoot.ownerDocument.defaultView.matchMedia(LANDSCAPE_MEDIA_QUERY);
// items list focus and search-on-type handling
this._nav.addEventListener("keydown", function onKeyCatchAll(aEvent) {
this._nav.addEventListener("keydown", (aEvent) => {
function getFocusedItemWithin(nav) {
let node = nav.ownerDocument.activeElement;
while (node && node.parentNode != nav) {
@ -77,7 +77,7 @@ this.SplitView = function SplitView(aRoot)
}
return false;
}
}.bind(this), false);
}, false);
}
SplitView.prototype = {
@ -196,10 +196,10 @@ SplitView.prototype = {
this._nav.appendChild(aSummary);
aSummary.addEventListener("click", function onSummaryClick(aEvent) {
aSummary.addEventListener("click", (aEvent) => {
aEvent.stopPropagation();
this.activeSummary = aSummary;
}.bind(this), false);
}, false);
this._side.appendChild(aDetails);

View File

@ -125,6 +125,16 @@ Telemetry.prototype = {
userHistogram: "DEVTOOLS_SHADEREDITOR_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_SHADEREDITOR_TIME_ACTIVE_SECONDS"
},
webaudioeditor: {
histogram: "DEVTOOLS_WEBAUDIOEDITOR_OPENED_BOOLEAN",
userHistogram: "DEVTOOLS_WEBAUDIOEDITOR_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_WEBAUDIOEDITOR_TIME_ACTIVE_SECONDS"
},
canvasdebugger: {
histogram: "DEVTOOLS_CANVASDEBUGGER_OPENED_BOOLEAN",
userHistogram: "DEVTOOLS_CANVASDEBUGGER_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_CANVASDEBUGGER_TIME_ACTIVE_SECONDS"
},
jsprofiler: {
histogram: "DEVTOOLS_JSPROFILER_OPENED_BOOLEAN",
userHistogram: "DEVTOOLS_JSPROFILER_OPENED_PER_USER_FLAG",

View File

@ -38,6 +38,8 @@ support-files =
[browser_telemetry_sidebar.js]
[browser_telemetry_toolbox.js]
[browser_telemetry_toolboxtabs_inspector.js]
[browser_telemetry_toolboxtabs_webaudioeditor.js]
[browser_telemetry_toolboxtabs_canvasdebugger.js]
[browser_telemetry_toolboxtabs_jsdebugger.js]
[browser_telemetry_toolboxtabs_jsprofiler.js]
[browser_telemetry_toolboxtabs_netmonitor.js]

View File

@ -0,0 +1,117 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolboxtabs_canvasdebugger.js</p>";
// Because we need to gather stats for the period of time that a tool has been
// opened we make use of setTimeout() to create tool active times.
const TOOL_DELAY = 200;
let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
let Telemetry = require("devtools/shared/telemetry");
let originalPref = Services.prefs.getBoolPref("devtools.canvasdebugger.enabled");
Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", true);
function init() {
Telemetry.prototype.telemetryInfo = {};
Telemetry.prototype._oldlog = Telemetry.prototype.log;
Telemetry.prototype.log = function(histogramId, value) {
if (!this.telemetryInfo) {
// Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
return;
}
if (histogramId) {
if (!this.telemetryInfo[histogramId]) {
this.telemetryInfo[histogramId] = [];
}
this.telemetryInfo[histogramId].push(value);
}
}
openToolboxTabTwice("canvasdebugger", false);
}
function openToolboxTabTwice(id, secondPass) {
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, id).then(function(toolbox) {
info("Toolbox tab " + id + " opened");
toolbox.once("destroyed", function() {
if (secondPass) {
checkResults();
} else {
openToolboxTabTwice(id, true);
}
});
// We use a timeout to check the tools active time
setTimeout(function() {
gDevTools.closeToolbox(target);
}, TOOL_DELAY);
}).then(null, reportError);
}
function checkResults() {
let result = Telemetry.prototype.telemetryInfo;
for (let [histId, value] of Iterator(result)) {
if (histId.endsWith("OPENED_PER_USER_FLAG")) {
ok(value.length === 1 && value[0] === true,
"Per user value " + histId + " has a single value of true");
} else if (histId.endsWith("OPENED_BOOLEAN")) {
ok(value.length > 1, histId + " has more than one entry");
let okay = value.every(function(element) {
return element === true;
});
ok(okay, "All " + histId + " entries are === true");
} else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
ok(value.length > 1, histId + " has more than one entry");
let okay = value.every(function(element) {
return element > 0;
});
ok(okay, "All " + histId + " entries have time > 0");
}
}
finishUp();
}
function reportError(error) {
let stack = " " + error.stack.replace(/\n?.*?@/g, "\n JS frame :: ");
ok(false, "ERROR: " + error + " at " + error.fileName + ":" +
error.lineNumber + "\n\nStack trace:" + stack);
finishUp();
}
function finishUp() {
gBrowser.removeCurrentTab();
Telemetry.prototype.log = Telemetry.prototype._oldlog;
delete Telemetry.prototype._oldlog;
delete Telemetry.prototype.telemetryInfo;
Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", originalPref);
TargetFactory = Services = promise = require = null;
finish();
}
function test() {
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function() {
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
waitForFocus(init, content);
}, true);
content.location = TEST_URI;
}

View File

@ -0,0 +1,118 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolboxtabs_webaudioeditor.js</p>";
// Because we need to gather stats for the period of time that a tool has been
// opened we make use of setTimeout() to create tool active times.
const TOOL_DELAY = 200;
let {Promise: promise} = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
let Telemetry = require("devtools/shared/telemetry");
let originalPref = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled");
Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true);
function init() {
Telemetry.prototype.telemetryInfo = {};
Telemetry.prototype._oldlog = Telemetry.prototype.log;
Telemetry.prototype.log = function(histogramId, value) {
if (!this.telemetryInfo) {
// Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
return;
}
if (histogramId) {
if (!this.telemetryInfo[histogramId]) {
this.telemetryInfo[histogramId] = [];
}
this.telemetryInfo[histogramId].push(value);
}
}
openToolboxTabTwice("webaudioeditor", false);
}
function openToolboxTabTwice(id, secondPass) {
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, id).then(function(toolbox) {
info("Toolbox tab " + id + " opened");
toolbox.once("destroyed", function() {
if (secondPass) {
checkResults();
} else {
openToolboxTabTwice(id, true);
}
});
// We use a timeout to check the tools active time
setTimeout(function() {
gDevTools.closeToolbox(target);
}, TOOL_DELAY);
}).then(null, reportError);
}
function checkResults() {
let result = Telemetry.prototype.telemetryInfo;
for (let [histId, value] of Iterator(result)) {
if (histId.endsWith("OPENED_PER_USER_FLAG")) {
ok(value.length === 1 && value[0] === true,
"Per user value " + histId + " has a single value of true");
} else if (histId.endsWith("OPENED_BOOLEAN")) {
ok(value.length > 1, histId + " has more than one entry");
let okay = value.every(function(element) {
return element === true;
});
ok(okay, "All " + histId + " entries are === true");
} else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
ok(value.length > 1, histId + " has more than one entry");
let okay = value.every(function(element) {
return element > 0;
});
ok(okay, "All " + histId + " entries have time > 0");
}
}
finishUp();
}
function reportError(error) {
let stack = " " + error.stack.replace(/\n?.*?@/g, "\n JS frame :: ");
ok(false, "ERROR: " + error + " at " + error.fileName + ":" +
error.lineNumber + "\n\nStack trace:" + stack);
finishUp();
}
function finishUp() {
gBrowser.removeCurrentTab();
Telemetry.prototype.log = Telemetry.prototype._oldlog;
delete Telemetry.prototype._oldlog;
delete Telemetry.prototype.telemetryInfo;
Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", originalPref);
TargetFactory = Services = promise = require = null;
finish();
}
function test() {
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function() {
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
waitForFocus(init, content);
}, true);
content.location = TEST_URI;
}

View File

@ -58,10 +58,10 @@ function runTest(index) {
var ais = is.bind(this);
function createTester(holder, options) {
return function() {
return () => {
ais(holder.innerHTML, options.later, options.name + ' later');
runNextTest();
}.bind(this);
};
}
executeSoon(createTester(holder, options));

View File

@ -134,13 +134,13 @@ StyleEditorUI.prototype = {
this._view = new SplitView(viewRoot);
wire(this._view.rootElement, ".style-editor-newButton", function onNew() {
wire(this._view.rootElement, ".style-editor-newButton", () =>{
this._debuggee.addStyleSheet(null).then(this._onStyleSheetCreated);
}.bind(this));
});
wire(this._view.rootElement, ".style-editor-importButton", function onImport() {
wire(this._view.rootElement, ".style-editor-importButton", ()=> {
this._importFromFile(this._mockImportFile || null, this._window);
}.bind(this));
});
this._optionsMenu = this._panelDoc.getElementById("style-editor-options-popup");
this._optionsMenu.addEventListener("popupshowing",
@ -300,7 +300,7 @@ StyleEditorUI.prototype = {
* Optional parent window for the file picker.
*/
_importFromFile: function(file, parentWindow) {
let onFileSelected = function(file) {
let onFileSelected = (file) => {
if (!file) {
// nothing selected
return;
@ -318,7 +318,7 @@ StyleEditorUI.prototype = {
});
});
}.bind(this);
};
showFilePicker(file, false, parentWindow, onFileSelected);
},
@ -430,11 +430,11 @@ StyleEditorUI.prototype = {
wire(summary, ".stylesheet-name", {
events: {
"keypress": function onStylesheetNameActivate(aEvent) {
"keypress": (aEvent) => {
if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
this._view.activeSummary = summary;
}
}.bind(this)
}
}
});
@ -494,7 +494,7 @@ StyleEditorUI.prototype = {
return;
}
let href = editor.styleSheet.href || editor.styleSheet.nodeHref;
let href = csscoverage.sheetToUrl(editor.styleSheet);
usage.createEditorReport(href).then(data => {
editor.removeAllUnusedRegions();

View File

@ -500,7 +500,7 @@ StyleSheetEditor.prototype = {
converter.charset = "UTF-8";
let istream = converter.convertToInputStream(this._state.text);
NetUtil.asyncCopy(istream, ostream, function onStreamCopied(status) {
NetUtil.asyncCopy(istream, ostream, (status) => {
if (!Components.isSuccessCode(status)) {
if (callback) {
callback(null);
@ -515,7 +515,7 @@ StyleSheetEditor.prototype = {
if (callback) {
callback(returnFile);
}
}.bind(this));
});
};
let defaultName;

View File

@ -429,10 +429,10 @@ CssHtmlTree.prototype = {
win.clearTimeout(this._filterChangedTimeout);
}
this._filterChangedTimeout = win.setTimeout(function() {
this._filterChangedTimeout = win.setTimeout(() => {
this.refreshPanel();
this._filterChangeTimeout = null;
}.bind(this), FILTER_CHANGED_TIMEOUT);
}, FILTER_CHANGED_TIMEOUT);
},
/**

View File

@ -18,6 +18,8 @@ const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devt
const EventEmitter = require("devtools/toolkit/event-emitter");
const STRINGS_URI = "chrome://browser/locale/devtools/webaudioeditor.properties"
const L10N = new ViewHelpers.L10N(STRINGS_URI);
const Telemetry = require("devtools/shared/telemetry");
const telemetry = new Telemetry();
let { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
@ -144,6 +146,7 @@ let WebAudioEditorController = {
* Listen for events emitted by the current tab target.
*/
initialize: function() {
telemetry.toolOpened("webaudioeditor");
this._onTabNavigated = this._onTabNavigated.bind(this);
this._onThemeChange = this._onThemeChange.bind(this);
gTarget.on("will-navigate", this._onTabNavigated);
@ -169,6 +172,7 @@ let WebAudioEditorController = {
* Remove events emitted by the current tab target.
*/
destroy: function() {
telemetry.toolClosed("webaudioeditor");
gTarget.off("will-navigate", this._onTabNavigated);
gTarget.off("navigate", this._onTabNavigated);
gFront.off("start-context", this._onStartContext);

View File

@ -286,7 +286,7 @@ NetworkPanel.prototype =
return a.name.toLowerCase() < b.name.toLowerCase();
});
aList.forEach(function(aItem) {
aList.forEach((aItem) => {
let name = aItem.name;
if (aIgnoreCookie && (name == "Cookie" || name == "Set-Cookie")) {
return;
@ -332,7 +332,7 @@ NetworkPanel.prototype =
row.appendChild(td);
parent.appendChild(row);
}.bind(this));
});
},
/**
@ -612,7 +612,7 @@ NetworkPanel.prototype =
let content = this.httpActivity.response.content;
let longString = this.webconsole.webConsoleClient.longString(content.text);
longString.substring(longString.initial.length, longString.length,
function NP__onLongStringSubstring(aResponse)
(aResponse) =>
{
if (aResponse.error) {
Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
@ -632,7 +632,7 @@ NetworkPanel.prototype =
this._appendTextNode("responseBody" + cached + "Content",
aResponse.substring);
}
}.bind(this));
});
},
/**
@ -779,7 +779,7 @@ NetworkPanel.prototype =
let postData = this.httpActivity.request.postData;
let longString = this.webconsole.webConsoleClient.longString(postData.text);
longString.substring(longString.initial.length, longString.length,
function NP__onLongStringSubstring(aResponse)
(aResponse) =>
{
if (aResponse.error) {
Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
@ -788,7 +788,7 @@ NetworkPanel.prototype =
postData.text = postData.text.initial + aResponse.substring;
this._updateRequestBody();
}.bind(this));
});
},
};

View File

@ -1850,7 +1850,7 @@ WebConsoleFrame.prototype = {
let actor = aHttpActivity.actor;
if (actor) {
this.webConsoleClient.getRequestHeaders(actor, function(aResponse) {
this.webConsoleClient.getRequestHeaders(actor, (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getRequestHeaders:" +
aResponse.error);
@ -1860,10 +1860,10 @@ WebConsoleFrame.prototype = {
aHttpActivity.request.headers = aResponse.headers;
this.webConsoleClient.getRequestCookies(actor, onRequestCookies);
}.bind(this));
});
}
let onRequestCookies = function(aResponse) {
let onRequestCookies = (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getRequestCookies:" +
aResponse.error);
@ -1873,9 +1873,9 @@ WebConsoleFrame.prototype = {
aHttpActivity.request.cookies = aResponse.cookies;
this.webConsoleClient.getResponseHeaders(actor, onResponseHeaders);
}.bind(this);
};
let onResponseHeaders = function(aResponse) {
let onResponseHeaders = (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getResponseHeaders:" +
aResponse.error);
@ -1885,9 +1885,9 @@ WebConsoleFrame.prototype = {
aHttpActivity.response.headers = aResponse.headers;
this.webConsoleClient.getResponseCookies(actor, onResponseCookies);
}.bind(this);
};
let onResponseCookies = function(aResponse) {
let onResponseCookies = (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getResponseCookies:" +
aResponse.error);
@ -1897,9 +1897,9 @@ WebConsoleFrame.prototype = {
aHttpActivity.response.cookies = aResponse.cookies;
this.webConsoleClient.getRequestPostData(actor, onRequestPostData);
}.bind(this);
};
let onRequestPostData = function(aResponse) {
let onRequestPostData = (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getRequestPostData:" +
aResponse.error);
@ -1910,9 +1910,9 @@ WebConsoleFrame.prototype = {
aHttpActivity.discardRequestBody = aResponse.postDataDiscarded;
this.webConsoleClient.getResponseContent(actor, onResponseContent);
}.bind(this);
};
let onResponseContent = function(aResponse) {
let onResponseContent = (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getResponseContent:" +
aResponse.error);
@ -1923,9 +1923,9 @@ WebConsoleFrame.prototype = {
aHttpActivity.discardResponseBody = aResponse.contentDiscarded;
this.webConsoleClient.getEventTimings(actor, onEventTimings);
}.bind(this);
};
let onEventTimings = function(aResponse) {
let onEventTimings = (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getEventTimings:" +
aResponse.error);
@ -1935,9 +1935,9 @@ WebConsoleFrame.prototype = {
aHttpActivity.timings = aResponse.timings;
openPanel();
}.bind(this);
};
let openPanel = function() {
let openPanel = () => {
aNode._netPanel = netPanel;
let panel = netPanel.panel;
@ -1953,7 +1953,7 @@ WebConsoleFrame.prototype = {
});
aNode._panelOpen = true;
}.bind(this);
};
let netPanel = new NetworkPanel(this.popupset, aHttpActivity, this);
netPanel.linkNode = aNode;
@ -2911,9 +2911,9 @@ WebConsoleFrame.prototype = {
this._commandController = null;
let onDestroy = function() {
let onDestroy = () => {
this._destroyer.resolve(null);
}.bind(this);
};
if (this.proxy) {
this.proxy.disconnect().then(onDestroy);
@ -4850,12 +4850,12 @@ WebConsoleConnectionProxy.prototype = {
timeout, Ci.nsITimer.TYPE_ONE_SHOT);
let connPromise = this._connectDefer.promise;
connPromise.then(function _onSucess() {
connPromise.then(() => {
this._connectTimer.cancel();
this._connectTimer = null;
}.bind(this), function _onFailure() {
}, () => {
this._connectTimer = null;
}.bind(this));
});
let client = this.client = this.target.client;

View File

@ -116,6 +116,7 @@ let UI = {
this.updateTitle();
this.updateCommands();
this.updateProjectButton();
this.updateProjectEditorHeader();
break;
};
},
@ -290,6 +291,25 @@ let UI = {
return this.projecteditor.loaded;
},
updateProjectEditorHeader: function() {
let project = AppManager.selectedProject;
if (!project || !this.projecteditor) {
return;
}
let status = project.validationStatus || "unknown";
if (status == "error warning") {
status = "error";
}
this.getProjectEditor().then((projecteditor) => {
projecteditor.setProjectToAppPath(project.location, {
name: project.name,
iconUrl: project.icon,
projectOverviewURL: "chrome://webide/content/details.xhtml",
validationStatus: status
});
}, console.error);
},
isProjectEditorEnabled: function() {
return Services.prefs.getBoolPref("devtools.webide.showProjectEditor");
},
@ -333,12 +353,8 @@ let UI = {
detailsIframe.setAttribute("hidden", "true");
projecteditorIframe.removeAttribute("hidden");
this.getProjectEditor().then((projecteditor) => {
projecteditor.setProjectToAppPath(project.location, {
name: project.name,
iconUrl: project.icon,
projectOverviewURL: "chrome://webide/content/details.xhtml"
});
this.getProjectEditor().then(() => {
this.updateProjectEditorHeader();
}, console.error);
if (project.location) {

View File

@ -1,4 +1,4 @@
This is the pdf.js project output, https://github.com/mozilla/pdf.js
Current extension version is: 1.0.277
Current extension version is: 1.0.370

View File

@ -66,7 +66,12 @@ var DEFAULT_PREFERENCES = {
defaultZoomValue: '',
sidebarViewOnLoad: 0,
enableHandToolOnLoad: false,
enableWebGL: false
enableWebGL: false,
disableRange: false,
disableAutoFetch: false,
disableFontFace: false,
disableTextLayer: false,
useOnlyCssZoom: false
};

View File

@ -21,8 +21,8 @@ if (typeof PDFJS === 'undefined') {
(typeof window !== 'undefined' ? window : this).PDFJS = {};
}
PDFJS.version = '1.0.277';
PDFJS.build = '250d394';
PDFJS.version = '1.0.370';
PDFJS.build = '13efe84';
(function pdfjsWrapper() {
// Use strict in our context only - users might not want it
@ -883,15 +883,15 @@ function isBool(v) {
}
function isInt(v) {
return typeof v == 'number' && ((v | 0) == v);
return typeof v === 'number' && ((v | 0) === v);
}
function isNum(v) {
return typeof v == 'number';
return typeof v === 'number';
}
function isString(v) {
return typeof v == 'string';
return typeof v === 'string';
}
function isNull(v) {
@ -903,7 +903,7 @@ function isName(v) {
}
function isCmd(v, cmd) {
return v instanceof Cmd && (!cmd || v.cmd == cmd);
return v instanceof Cmd && (cmd === undefined || v.cmd === cmd);
}
function isDict(v, type) {
@ -914,7 +914,7 @@ function isDict(v, type) {
return true;
}
var dictType = v.get('Type');
return isName(dictType) && dictType.name == type;
return isName(dictType) && dictType.name === type;
}
function isArray(v) {
@ -922,13 +922,11 @@ function isArray(v) {
}
function isStream(v) {
return typeof v == 'object' && v !== null && v !== undefined &&
('getBytes' in v);
return typeof v === 'object' && v !== null && v.getBytes !== undefined;
}
function isArrayBuffer(v) {
return typeof v == 'object' && v !== null && v !== undefined &&
('byteLength' in v);
return typeof v === 'object' && v !== null && v.byteLength !== undefined;
}
function isRef(v) {
@ -1414,7 +1412,7 @@ var Annotation = (function AnnotationClosure() {
data &&
(!data.annotationFlags ||
!(data.annotationFlags & 0x22)) && // Hidden or NoView
data.rect); // rectangle is nessessary
data.rect); // rectangle is necessary
},
isPrintable: function Annotation_isPrintable() {
@ -1423,7 +1421,8 @@ var Annotation = (function AnnotationClosure() {
data &&
data.annotationFlags && // Default: not printable
data.annotationFlags & 0x4 && // Print
data.rect); // rectangle is nessessary
!(data.annotationFlags & 0x2) && // Hidden
data.rect); // rectangle is necessary
},
loadResources: function Annotation_loadResources(keys) {
@ -1548,7 +1547,9 @@ var Annotation = (function AnnotationClosure() {
if (annotation.isViewable() || annotation.isPrintable()) {
return annotation;
} else {
warn('unimplemented annotation type: ' + subtype);
if (SUPPORTED_TYPES.indexOf(subtype) === -1) {
warn('unimplemented annotation type: ' + subtype);
}
}
};
@ -2150,6 +2151,13 @@ PDFJS.disableCreateObjectURL = (PDFJS.disableCreateObjectURL === undefined ?
PDFJS.disableWebGL = (PDFJS.disableWebGL === undefined ?
true : PDFJS.disableWebGL);
/**
* Enables CSS only zooming.
* @var {boolean}
*/
PDFJS.useOnlyCssZoom = (PDFJS.useOnlyCssZoom === undefined ?
false : PDFJS.useOnlyCssZoom);
/**
* Controls the logging level.
* The constants from PDFJS.VERBOSITY_LEVELS should be used:
@ -2161,6 +2169,14 @@ PDFJS.disableWebGL = (PDFJS.disableWebGL === undefined ?
PDFJS.verbosity = (PDFJS.verbosity === undefined ?
PDFJS.VERBOSITY_LEVELS.warnings : PDFJS.verbosity);
/**
* The maximum supported canvas size in total pixels e.g. width * height.
* The default value is 4096 * 4096. Use -1 for no limit.
* @var {number}
*/
PDFJS.maxCanvasPixels = (PDFJS.maxCanvasPixels === undefined ?
16777216 : PDFJS.maxCanvasPixels);
/**
* Document initialization / loading parameters object.
*
@ -3793,6 +3809,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
// Defines the time the executeOperatorList is going to be executing
// before it stops and shedules a continue of execution.
var EXECUTION_TIME = 15;
// Defines the number of steps before checking the execution time
var EXECUTION_STEPS = 10;
function CanvasGraphics(canvasCtx, commonObjs, objs, imageLayer) {
this.ctx = canvasCtx;
@ -3998,49 +4016,54 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}
}
function composeSMaskBackdrop(bytes, r0, g0, b0) {
var length = bytes.length;
for (var i = 3; i < length; i += 4) {
var alpha = bytes[i];
if (alpha === 0) {
bytes[i - 3] = r0;
bytes[i - 2] = g0;
bytes[i - 1] = b0;
} else if (alpha < 255) {
var alpha_ = 255 - alpha;
bytes[i - 3] = (bytes[i - 3] * alpha + r0 * alpha_) >> 8;
bytes[i - 2] = (bytes[i - 2] * alpha + g0 * alpha_) >> 8;
bytes[i - 1] = (bytes[i - 1] * alpha + b0 * alpha_) >> 8;
}
}
}
function composeSMaskAlpha(maskData, layerData) {
var length = maskData.length;
var scale = 1 / 255;
for (var i = 3; i < length; i += 4) {
var alpha = maskData[i];
layerData[i] = (layerData[i] * alpha * scale) | 0;
}
}
function composeSMaskLuminosity(maskData, layerData) {
var length = maskData.length;
for (var i = 3; i < length; i += 4) {
var y = ((maskData[i - 3] * 77) + // * 0.3 / 255 * 0x10000
(maskData[i - 2] * 152) + // * 0.59 ....
(maskData[i - 1] * 28)) | 0; // * 0.11 ....
layerData[i] = (layerData[i] * y) >> 16;
}
}
function genericComposeSMask(maskCtx, layerCtx, width, height,
subtype, backdrop) {
var addBackdropFn;
if (backdrop) {
addBackdropFn = function (r0, g0, b0, bytes) {
var length = bytes.length;
for (var i = 3; i < length; i += 4) {
var alpha = bytes[i] / 255;
if (alpha === 0) {
bytes[i - 3] = r0;
bytes[i - 2] = g0;
bytes[i - 1] = b0;
} else if (alpha < 1) {
var alpha_ = 1 - alpha;
bytes[i - 3] = (bytes[i - 3] * alpha + r0 * alpha_) | 0;
bytes[i - 2] = (bytes[i - 2] * alpha + g0 * alpha_) | 0;
bytes[i - 1] = (bytes[i - 1] * alpha + b0 * alpha_) | 0;
}
}
}.bind(null, backdrop[0], backdrop[1], backdrop[2]);
} else {
addBackdropFn = function () {};
}
var hasBackdrop = !!backdrop;
var r0 = hasBackdrop ? backdrop[0] : 0;
var g0 = hasBackdrop ? backdrop[1] : 0;
var b0 = hasBackdrop ? backdrop[2] : 0;
var composeFn;
if (subtype === 'Luminosity') {
composeFn = function (maskDataBytes, layerDataBytes) {
var length = maskDataBytes.length;
for (var i = 3; i < length; i += 4) {
var y = ((maskDataBytes[i - 3] * 77) + // * 0.3 / 255 * 0x10000
(maskDataBytes[i - 2] * 152) + // * 0.59 ....
(maskDataBytes[i - 1] * 28)) | 0; // * 0.11 ....
layerDataBytes[i] = (layerDataBytes[i] * y) >> 16;
}
};
composeFn = composeSMaskLuminosity;
} else {
composeFn = function (maskDataBytes, layerDataBytes) {
var length = maskDataBytes.length;
for (var i = 3; i < length; i += 4) {
var alpha = maskDataBytes[i];
layerDataBytes[i] = (layerDataBytes[i] * alpha / 255) | 0;
}
};
composeFn = composeSMaskAlpha;
}
// processing image in chunks to save memory
@ -4051,7 +4074,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var maskData = maskCtx.getImageData(0, row, width, chunkHeight);
var layerData = layerCtx.getImageData(0, row, width, chunkHeight);
addBackdropFn(maskData.data);
if (hasBackdrop) {
composeSMaskBackdrop(maskData.data, r0, g0, b0);
}
composeFn(maskData.data, layerData.data);
maskCtx.putImageData(layerData, 0, row);
@ -4125,18 +4150,21 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var argsArrayLen = argsArray.length;
// Sometimes the OperatorList to execute is empty.
if (argsArrayLen == i) {
if (argsArrayLen === i) {
return i;
}
var endTime = Date.now() + EXECUTION_TIME;
var chunkOperations = (argsArrayLen - i > EXECUTION_STEPS &&
typeof continueCallback === 'function');
var endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0;
var steps = 0;
var commonObjs = this.commonObjs;
var objs = this.objs;
var fnId;
while (true) {
if (stepper && i === stepper.nextBreakPoint) {
if (stepper !== undefined && i === stepper.nextBreakPoint) {
stepper.breakIt(i, continueCallback);
return i;
}
@ -4149,16 +4177,13 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var deps = argsArray[i];
for (var n = 0, nn = deps.length; n < nn; n++) {
var depObjId = deps[n];
var common = depObjId.substring(0, 2) == 'g_';
var common = depObjId[0] === 'g' && depObjId[1] === '_';
var objsPool = common ? commonObjs : objs;
// If the promise isn't resolved yet, add the continueCallback
// to the promise and bail out.
if (!common && !objs.isResolved(depObjId)) {
objs.get(depObjId, continueCallback);
return i;
}
if (common && !commonObjs.isResolved(depObjId)) {
commonObjs.get(depObjId, continueCallback);
if (!objsPool.isResolved(depObjId)) {
objsPool.get(depObjId, continueCallback);
return i;
}
}
@ -4167,15 +4192,18 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
i++;
// If the entire operatorList was executed, stop as were done.
if (i == argsArrayLen) {
if (i === argsArrayLen) {
return i;
}
// If the execution took longer then a certain amount of time and
// `continueCallback` is specified, interrupt the execution.
if (continueCallback && Date.now() > endTime) {
continueCallback();
return i;
if (chunkOperations && ++steps > EXECUTION_STEPS) {
if (Date.now() > endTime) {
continueCallback();
return i;
}
steps = 0;
}
// If the operatorList isn't executed completely yet OR the execution
@ -4334,18 +4362,15 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var old = this.current;
this.stateStack.push(old);
this.current = old.clone();
if (this.current.activeSMask) {
this.current.activeSMask = null;
}
this.current.activeSMask = null;
},
restore: function CanvasGraphics_restore() {
var prev = this.stateStack.pop();
if (prev) {
if (this.current.activeSMask) {
if (this.stateStack.length !== 0) {
if (this.current.activeSMask !== null) {
this.endSMaskGroup();
}
this.current = prev;
this.current = this.stateStack.pop();
this.ctx.restore();
}
},
@ -4901,7 +4926,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
// Color
getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR) {
var pattern;
if (IR[0] == 'TilingPattern') {
if (IR[0] === 'TilingPattern') {
var color = IR[1];
pattern = new TilingPattern(IR, color, this.ctx, this.objs,
this.commonObjs, this.baseTransform);
@ -4977,13 +5002,13 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.save();
this.baseTransformStack.push(this.baseTransform);
if (matrix && isArray(matrix) && 6 == matrix.length) {
if (isArray(matrix) && 6 === matrix.length) {
this.transform.apply(this, matrix);
}
this.baseTransform = this.ctx.mozCurrentTransform;
if (bbox && isArray(bbox) && 4 == bbox.length) {
if (isArray(bbox) && 4 === bbox.length) {
var width = bbox[2] - bbox[0];
var height = bbox[3] - bbox[1];
this.rectangle(bbox[0], bbox[1], width, height);
@ -5135,7 +5160,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
matrix) {
this.save();
if (rect && isArray(rect) && 4 == rect.length) {
if (isArray(rect) && 4 === rect.length) {
var width = rect[2] - rect[0];
var height = rect[3] - rect[1];
this.rectangle(rect[0], rect[1], width, height);
@ -5453,7 +5478,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
consumePath: function CanvasGraphics_consumePath() {
var ctx = this.ctx;
if (this.pendingClip) {
if (this.pendingClip == EO_CLIP) {
if (this.pendingClip === EO_CLIP) {
if (ctx.mozFillRule !== undefined) {
ctx.mozFillRule = 'evenodd';
ctx.clip();

File diff suppressed because it is too large Load Diff

View File

@ -57,8 +57,7 @@ var NetworkManager = (function NetworkManagerClosure() {
}
function getArrayBuffer(xhr) {
var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
xhr.responseArrayBuffer || xhr.response);
var data = xhr.response;
if (typeof data !== 'string') {
return data;
}
@ -110,7 +109,7 @@ var NetworkManager = (function NetworkManagerClosure() {
pendingRequest.expectedStatus = 200;
}
xhr.mozResponseType = xhr.responseType = 'arraybuffer';
xhr.responseType = 'arraybuffer';
if (args.onProgress) {
xhr.onprogress = args.onProgress;

View File

@ -217,6 +217,8 @@ http://sourceforge.net/adobe/cmap/wiki/License/
<option title="" value="1.25">125%</option>
<option title="" value="1.5">150%</option>
<option title="" value="2">200%</option>
<option title="" value="3">300%</option>
<option title="" value="4">400%</option>
</select>
</span>
</div>

View File

@ -28,18 +28,17 @@ var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
var DEFAULT_SCALE = 'auto';
var DEFAULT_SCALE_DELTA = 1.1;
var UNKNOWN_SCALE = 0;
var CACHE_SIZE = 20;
var CACHE_SIZE = 10;
var CSS_UNITS = 96.0 / 72.0;
var SCROLLBAR_PADDING = 40;
var VERTICAL_PADDING = 5;
var MAX_AUTO_SCALE = 1.25;
var MIN_SCALE = 0.25;
var MAX_SCALE = 4.0;
var MAX_SCALE = 10.0;
var VIEW_HISTORY_MEMORY = 20;
var SCALE_SELECT_CONTAINER_PADDING = 8;
var SCALE_SELECT_PADDING = 22;
var THUMBNAIL_SCROLL_MARGIN = -19;
var USE_ONLY_CSS_ZOOM = false;
var CLEANUP_TIMEOUT = 30000;
var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
var RenderingStates = {
@ -295,7 +294,7 @@ var Cache = function cacheCache(size) {
this.push = function cachePush(view) {
var i = data.indexOf(view);
if (i >= 0) {
data.splice(i);
data.splice(i, 1);
}
data.push(view);
if (data.length > size) {
@ -312,7 +311,12 @@ var DEFAULT_PREFERENCES = {
defaultZoomValue: '',
sidebarViewOnLoad: 0,
enableHandToolOnLoad: false,
enableWebGL: false
enableWebGL: false,
disableRange: false,
disableAutoFetch: false,
disableFontFace: false,
disableTextLayer: false,
useOnlyCssZoom: false
};
@ -2635,12 +2639,36 @@ var PDFView = {
var initializedPromise = Promise.all([
Preferences.get('enableWebGL').then(function resolved(value) {
PDFJS.disableWebGL = !value;
}, function rejected(reason) {}),
}),
Preferences.get('sidebarViewOnLoad').then(function resolved(value) {
self.preferenceSidebarViewOnLoad = value;
}, function rejected(reason) {})
}),
Preferences.get('disableTextLayer').then(function resolved(value) {
if (PDFJS.disableTextLayer === true) {
return;
}
PDFJS.disableTextLayer = value;
}),
Preferences.get('disableRange').then(function resolved(value) {
if (PDFJS.disableRange === true) {
return;
}
PDFJS.disableRange = value;
}),
Preferences.get('disableAutoFetch').then(function resolved(value) {
PDFJS.disableAutoFetch = value;
}),
Preferences.get('disableFontFace').then(function resolved(value) {
if (PDFJS.disableFontFace === true) {
return;
}
PDFJS.disableFontFace = value;
}),
Preferences.get('useOnlyCssZoom').then(function resolved(value) {
PDFJS.useOnlyCssZoom = value;
})
// TODO move more preferences and other async stuff here
]);
]).catch(function (reason) { });
return initializedPromise.then(function () {
PDFView.initialized = true;
@ -3584,6 +3612,8 @@ var PDFView = {
}
}
this.pdfDocument.cleanup();
ThumbnailView.tempImageCache = null;
},
getHighestPriority: function pdfViewGetHighestPriority(visible, views,
@ -3652,6 +3682,9 @@ var PDFView = {
},
setHash: function pdfViewSetHash(hash) {
var validFitZoomValues = ['Fit','FitB','FitH','FitBH',
'FitV','FitBV','FitR'];
if (!hash) {
return;
}
@ -3676,10 +3709,13 @@ var PDFView = {
// it should stay as it is.
var zoomArg = zoomArgs[0];
var zoomArgNumber = parseFloat(zoomArg);
var destName = 'XYZ';
if (zoomArgNumber) {
zoomArg = zoomArgNumber / 100;
} else if (validFitZoomValues.indexOf(zoomArg) >= 0) {
destName = zoomArg;
}
dest = [null, {name: 'XYZ'},
dest = [null, { name: destName },
zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
zoomArg];
@ -3988,6 +4024,7 @@ var PageView = function pageView(container, id, scale,
this.scale = scale || 1.0;
this.viewport = defaultViewport;
this.pdfPageRotate = defaultViewport.rotation;
this.hasRestrictedScaling = false;
this.renderingState = RenderingStates.INITIAL;
this.resume = null;
@ -4058,7 +4095,13 @@ var PageView = function pageView(container, id, scale,
this.annotationLayer = null;
}
delete this.canvas;
if (this.canvas) {
// Zeroing the width and height causes Firefox to release graphics
// resources immediately, which can greatly reduce memory consumption.
this.canvas.width = 0;
this.canvas.height = 0;
delete this.canvas;
}
this.loadingIconDiv = document.createElement('div');
this.loadingIconDiv.className = 'loadingIcon';
@ -4078,8 +4121,23 @@ var PageView = function pageView(container, id, scale,
rotation: totalRotation
});
if (USE_ONLY_CSS_ZOOM && this.canvas) {
this.cssTransform(this.canvas);
var isScalingRestricted = false;
if (this.canvas && PDFJS.maxCanvasPixels > 0) {
var ctx = this.canvas.getContext('2d');
var outputScale = getOutputScale(ctx);
var pixelsInViewport = this.viewport.width * this.viewport.height;
var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
PDFJS.maxCanvasPixels) {
isScalingRestricted = true;
}
}
if (this.canvas &&
(PDFJS.useOnlyCssZoom ||
(this.hasRestrictedScaling && isScalingRestricted))) {
this.cssTransform(this.canvas, true);
return;
} else if (this.canvas && !this.zoomLayer) {
this.zoomLayer = this.canvas.parentNode;
@ -4091,7 +4149,7 @@ var PageView = function pageView(container, id, scale,
this.reset(true);
};
this.cssTransform = function pageCssTransform(canvas) {
this.cssTransform = function pageCssTransform(canvas, redrawAnnotations) {
// Scale canvas, canvas wrapper, and page container.
var width = this.viewport.width;
var height = this.viewport.height;
@ -4154,7 +4212,7 @@ var PageView = function pageView(container, id, scale,
CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
}
if (USE_ONLY_CSS_ZOOM && this.annotationLayer) {
if (redrawAnnotations && this.annotationLayer) {
setupAnnotations(div, this.pdfPage, this.viewport);
}
};
@ -4451,7 +4509,7 @@ var PageView = function pageView(container, id, scale,
var ctx = canvas.getContext('2d');
var outputScale = getOutputScale(ctx);
if (USE_ONLY_CSS_ZOOM) {
if (PDFJS.useOnlyCssZoom) {
var actualSizeViewport = viewport.clone({ scale: CSS_UNITS });
// Use a scale that will make the canvas be the original intended size
// of the page.
@ -4460,6 +4518,19 @@ var PageView = function pageView(container, id, scale,
outputScale.scaled = true;
}
if (PDFJS.maxCanvasPixels > 0) {
var pixelsInViewport = viewport.width * viewport.height;
var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
outputScale.sx = maxScale;
outputScale.sy = maxScale;
outputScale.scaled = true;
this.hasRestrictedScaling = true;
} else {
this.hasRestrictedScaling = false;
}
}
canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0;
canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0;
canvas.style.width = Math.floor(viewport.width) + 'px';
@ -4542,8 +4613,6 @@ var PageView = function pageView(container, id, scale,
self.onAfterDraw();
}
cache.push(self);
var event = document.createEvent('CustomEvent');
event.initCustomEvent('pagerender', true, true, {
pageNumber: pdfPage.pageNumber
@ -4594,6 +4663,10 @@ var PageView = function pageView(container, id, scale,
setupAnnotations(div, pdfPage, this.viewport);
div.setAttribute('data-loaded', true);
// Add the page to the cache at the start of drawing. That way it can be
// evicted from the cache and destroyed even if we pause its rendering.
cache.push(this);
};
this.beforePrint = function pageViewBeforePrint() {
@ -4820,6 +4893,17 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
this.hasImage = true;
};
function getTempCanvas(width, height) {
var tempCanvas = ThumbnailView.tempImageCache;
if (!tempCanvas) {
tempCanvas = document.createElement('canvas');
ThumbnailView.tempImageCache = tempCanvas;
}
tempCanvas.width = width;
tempCanvas.height = height;
return tempCanvas;
}
this.setImage = function thumbnailViewSetImage(img) {
if (!this.pdfPage) {
var promise = PDFView.getPage(this.id);
@ -4834,13 +4918,40 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
}
this.renderingState = RenderingStates.FINISHED;
var ctx = this.getPageDrawContext();
ctx.drawImage(img, 0, 0, img.width, img.height,
var reducedImage = img;
var reducedWidth = img.width;
var reducedHeight = img.height;
// drawImage does an awful job of rescaling the image, doing it gradually
var MAX_SCALE_FACTOR = 2.0;
if (Math.max(img.width / ctx.canvas.width,
img.height / ctx.canvas.height) > MAX_SCALE_FACTOR) {
reducedWidth >>= 1;
reducedHeight >>= 1;
reducedImage = getTempCanvas(reducedWidth, reducedHeight);
var reducedImageCtx = reducedImage.getContext('2d');
reducedImageCtx.drawImage(img, 0, 0, img.width, img.height,
0, 0, reducedWidth, reducedHeight);
while (Math.max(reducedWidth / ctx.canvas.width,
reducedHeight / ctx.canvas.height) > MAX_SCALE_FACTOR) {
reducedImageCtx.drawImage(reducedImage,
0, 0, reducedWidth, reducedHeight,
0, 0, reducedWidth >> 1, reducedHeight >> 1);
reducedWidth >>= 1;
reducedHeight >>= 1;
}
}
ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight,
0, 0, ctx.canvas.width, ctx.canvas.height);
this.hasImage = true;
};
};
ThumbnailView.tempImageCache = null;
var FIND_SCROLL_OFFSET_TOP = -50;
var FIND_SCROLL_OFFSET_LEFT = -400;
@ -5301,7 +5412,7 @@ function webViewerInitialized() {
}
if ('useOnlyCssZoom' in hashParams) {
USE_ONLY_CSS_ZOOM = (hashParams['useOnlyCssZoom'] === 'true');
PDFJS.useOnlyCssZoom = (hashParams['useOnlyCssZoom'] === 'true');
}
if ('verbosity' in hashParams) {
@ -5819,13 +5930,14 @@ window.addEventListener('keydown', function keydown(evt) {
break;
case 36: // home
if (PresentationMode.active) {
if (PresentationMode.active || PDFView.page > 1) {
PDFView.page = 1;
handled = true;
}
break;
case 35: // end
if (PresentationMode.active) {
if (PresentationMode.active ||
PDFView.page < PDFView.pdfDocument.numPages) {
PDFView.page = PDFView.pdfDocument.numPages;
handled = true;
}

View File

@ -168,8 +168,8 @@ helpManRequired=required
helpManOptional=optional
helpManDefault=optional, default=%S
# LOCALIZATION NOTE (helpIntro): This forms part of the output from the 'help'
# command. 'GCLI' is a project name and should be left untranslated.
# LOCALIZATION NOTE: This forms part of the output from the 'help' command.
# 'GCLI' is a project name and should be left untranslated.
helpIntro=GCLI is an experiment to create a highly usable command line for web developers.
# LOCALIZATION NOTE: Text shown as part of the output of the 'help' command

View File

@ -68,8 +68,8 @@
<!-- LOCALIZATION NOTE (options.enableChrome.label4): This is the label for the
- checkbox that toggles chrome debugging, i.e. devtools.chrome.enabled
- boolean preference in about:config, in the options panel. -->
<!ENTITY options.enableChrome.label4 "Enable chrome and addon debugging">
<!ENTITY options.enableChrome.tooltip2 "Turning this option on will allow you to use various developer tools in browser context and debug addons from the Add-On Manager">
<!ENTITY options.enableChrome.label4 "Enable chrome and add-on debugging">
<!ENTITY options.enableChrome.tooltip2 "Turning this option on will allow you to use various developer tools in browser context and debug add-ons from the Add-ons Manager">
<!-- LOCALIZATION NOTE (options.enableRemote.label3): This is the label for the
- checkbox that toggles remote debugging, i.e. devtools.debugger.remote-enabled

View File

@ -687,6 +687,7 @@ filefield {
border-radius: 2px;
background-color: #FBFBFB;
overflow-y: auto;
height: 500px;
}
#typeColumn,

View File

@ -488,6 +488,26 @@ public:
mStatsShowing = aShow;
}
bool MozAllowCasting() const
{
return mAllowCasting;
}
void SetMozAllowCasting(bool aShow)
{
mAllowCasting = aShow;
}
bool MozIsCasting() const
{
return mIsCasting;
}
void SetMozIsCasting(bool aShow)
{
mIsCasting = aShow;
}
already_AddRefed<DOMMediaStream> GetMozSrcObject() const;
void SetMozSrcObject(DOMMediaStream& aValue);
@ -1093,6 +1113,14 @@ protected:
// video controls
bool mStatsShowing;
// The following two fields are here for the private storage of the builtin
// video controls, and control 'casting' of the video to external devices
// (TVs, projectors etc.)
// True if casting is currently allowed
bool mAllowCasting;
// True if currently casting this video
bool mIsCasting;
// True if the sound is being captured.
bool mAudioCaptured;

View File

@ -1992,6 +1992,8 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<nsINodeInfo>& aNodeInfo)
mPaused(true),
mMuted(0),
mStatsShowing(false),
mAllowCasting(false),
mIsCasting(false),
mAudioCaptured(false),
mPlayingBeforeSeek(false),
mPausedForInactiveDocumentOrChannel(false),

View File

@ -104,6 +104,8 @@ partial interface HTMLMediaElement {
// NB: for internal use with the video controls:
[Func="IsChromeOrXBL"] attribute boolean mozMediaStatisticsShowing;
[Func="IsChromeOrXBL"] attribute boolean mozAllowCasting;
[Func="IsChromeOrXBL"] attribute boolean mozIsCasting;
// Mozilla extension: stream capture
[Throws]

View File

@ -487,7 +487,7 @@ abstract public class BrowserApp extends GeckoApp
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT);
}
((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideTabsTouchListener());
((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideOnTouchListener());
((GeckoApp.MainLayout) mMainLayout).setMotionEventInterceptor(new MotionEventInterceptor() {
@Override
public boolean onInterceptMotionEvent(View view, MotionEvent event) {
@ -1985,11 +1985,20 @@ abstract public class BrowserApp extends GeckoApp
mBrowserSearch.setUserVisibleHint(false);
}
private class HideTabsTouchListener implements TouchEventInterceptor {
/**
* Hides certain UI elements (e.g. button toast, tabs tray) when the
* user touches the main layout.
*/
private class HideOnTouchListener implements TouchEventInterceptor {
private boolean mIsHidingTabs = false;
@Override
public boolean onInterceptTouchEvent(View view, MotionEvent event) {
// Only try to hide the button toast if it's already inflated.
if (mToast != null) {
mToast.hide(false, ButtonToast.ReasonHidden.TOUCH_OUTSIDE);
}
// We need to account for scroll state for the touched view otherwise
// tapping on an "empty" part of the view will still be considered a
// valid touch event.
@ -2627,10 +2636,25 @@ abstract public class BrowserApp extends GeckoApp
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String action = intent.getAction();
final boolean isViewAction = Intent.ACTION_VIEW.equals(action);
final boolean isBookmarkAction = GeckoApp.ACTION_BOOKMARK.equals(action);
if (mInitialized && (isViewAction || isBookmarkAction)) {
// Dismiss editing mode if the user is loading a URL from an external app.
mBrowserToolbar.cancelEdit();
// GeckoApp.ACTION_BOOKMARK means we're opening a bookmark that
// was added to Android's homescreen.
final TelemetryContract.Method method =
(isViewAction ? TelemetryContract.Method.INTENT : TelemetryContract.Method.HOMESCREEN);
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, method);
}
super.onNewIntent(intent);
if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 10 && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
String uri = intent.getDataString();
GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
@ -2640,14 +2664,6 @@ abstract public class BrowserApp extends GeckoApp
return;
}
if (Intent.ACTION_VIEW.equals(action)) {
// Dismiss editing mode if the user is loading a URL from an external app.
mBrowserToolbar.cancelEdit();
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT);
return;
}
// Only solicit feedback when the app has been launched from the icon shortcut.
if (!Intent.ACTION_MAIN.equals(action)) {
return;

View File

@ -126,6 +126,9 @@ public interface TelemetryContract {
// Action occurred via an intent.
INTENT("intent"),
// Action occurred via a homescreen launcher.
HOMESCREEN("homescreen"),
// Action triggered from a list.
LIST("list"),

View File

@ -119,6 +119,12 @@ public class AnnouncementsBroadcastService extends BackgroundService {
@Override
protected void onHandleIntent(Intent intent) {
Logger.setThreadLogTag(AnnouncementsConstants.GLOBAL_LOG_TAG);
// Intent can be null. Bug 1025937.
if (intent == null) {
Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
}
final String action = intent.getAction();
Logger.debug(LOG_TAG, "Broadcast onReceive. Intent is " + action);
@ -143,8 +149,10 @@ public class AnnouncementsBroadcastService extends BackgroundService {
* Handle the intent sent by the browser when it wishes to notify us
* of the value of the user preference. Look at the value and toggle the
* alarm service accordingly.
*
* @param intent must be non-null.
*/
protected void handlePrefIntent(Intent intent) {
private void handlePrefIntent(Intent intent) {
if (!intent.hasExtra("enabled")) {
Logger.warn(LOG_TAG, "Got ANNOUNCEMENTS_PREF intent without enabled. Ignoring.");
return;

View File

@ -117,6 +117,12 @@ public class AnnouncementsService extends BackgroundService implements Announcem
@Override
public void onHandleIntent(Intent intent) {
Logger.setThreadLogTag(AnnouncementsConstants.GLOBAL_LOG_TAG);
// Intent can be null. Bug 1025937.
if (intent == null) {
Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
}
Logger.debug(LOG_TAG, "Running AnnouncementsService.");
if (AnnouncementsConstants.DISABLED) {

View File

@ -100,6 +100,11 @@ public class HealthReportBroadcastService extends BackgroundService {
protected void onHandleIntent(Intent intent) {
Logger.setThreadLogTag(HealthReportConstants.GLOBAL_LOG_TAG);
// Intent can be null. Bug 1025937.
if (intent == null) {
Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
}
// The same intent can be handled by multiple methods so do not short-circuit evaluate.
boolean handled = attemptHandleIntentForUpload(intent);
handled = attemptHandleIntentForPrune(intent) ? true : handled;
@ -111,8 +116,10 @@ public class HealthReportBroadcastService extends BackgroundService {
/**
* Attempts to handle the given intent for FHR document upload. If it cannot, false is returned.
*
* @param intent must be non-null.
*/
protected boolean attemptHandleIntentForUpload(final Intent intent) {
private boolean attemptHandleIntentForUpload(final Intent intent) {
if (HealthReportConstants.UPLOAD_FEATURE_DISABLED) {
Logger.debug(LOG_TAG, "Health report upload feature is compile-time disabled; not handling intent.");
return false;
@ -142,8 +149,10 @@ public class HealthReportBroadcastService extends BackgroundService {
* Handle the intent sent by the browser when it wishes to notify us
* of the value of the user preference. Look at the value and toggle the
* alarm service accordingly.
*
* @param intent must be non-null.
*/
protected void handleUploadPrefIntent(Intent intent) {
private void handleUploadPrefIntent(Intent intent) {
if (!intent.hasExtra("enabled")) {
Logger.warn(LOG_TAG, "Got " + HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF + " intent without enabled. Ignoring.");
return;
@ -194,8 +203,10 @@ public class HealthReportBroadcastService extends BackgroundService {
/**
* Attempts to handle the given intent for FHR data pruning. If it cannot, false is returned.
*
* @param intent must be non-null.
*/
protected boolean attemptHandleIntentForPrune(final Intent intent) {
private boolean attemptHandleIntentForPrune(final Intent intent) {
final String action = intent.getAction();
Logger.debug(LOG_TAG, "Prune: Attempting to handle intent with action, " + action + ".");
@ -215,7 +226,10 @@ public class HealthReportBroadcastService extends BackgroundService {
return false;
}
protected void handlePruneIntent(final Intent intent) {
/**
* @param intent must be non-null.
*/
private void handlePruneIntent(final Intent intent) {
final String profileName = intent.getStringExtra("profileName");
final String profilePath = intent.getStringExtra("profilePath");

View File

@ -39,6 +39,12 @@ public class HealthReportPruneService extends BackgroundService {
@Override
public void onHandleIntent(Intent intent) {
Logger.setThreadLogTag(HealthReportConstants.GLOBAL_LOG_TAG);
// Intent can be null. Bug 1025937.
if (intent == null) {
Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
}
Logger.debug(LOG_TAG, "Handling prune intent.");
if (!isIntentValid(intent)) {
@ -59,7 +65,11 @@ public class HealthReportPruneService extends BackgroundService {
return new PrunePolicy(storage, getSharedPreferences());
}
protected boolean isIntentValid(final Intent intent) {
/**
* @param intent must be non-null.
* @return true if the supplied intent contains both profileName and profilePath.
*/
private static boolean isIntentValid(final Intent intent) {
boolean isValid = true;
final String profileName = intent.getStringExtra("profileName");

View File

@ -43,6 +43,11 @@ public class HealthReportUploadService extends BackgroundService {
public void onHandleIntent(Intent intent) {
Logger.setThreadLogTag(HealthReportConstants.GLOBAL_LOG_TAG);
// Intent can be null. Bug 1025937.
if (intent == null) {
Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
}
if (HealthReportConstants.UPLOAD_FEATURE_DISABLED) {
Logger.debug(LOG_TAG, "Health report upload feature is compile-time disabled; not handling upload intent.");
return;

View File

@ -29,6 +29,11 @@ public class FxAccountDeletedService extends IntentService {
@Override
protected void onHandleIntent(final Intent intent) {
// Intent can, in theory, be null. Bug 1025937.
if (intent == null) {
Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
}
final Context context = this;
long intentVersion = intent.getLongExtra(

View File

@ -371,6 +371,10 @@ size. -->
previous location in the navigation, such as the previous folder -->
<!ENTITY home_move_up_to_filter "Up to &formatS;">
<!ENTITY private_browsing_title "Private Browsing">
<!ENTITY private_tabs_panel_description "Your private tabs will show up here. While we don\'t keep any form of your browsing history or cookies, files that you download will still be saved on your device.">
<!ENTITY private_tabs_panel_learn_more "Want to learn more?">
<!ENTITY pin_site_dialog_hint "Enter a search keyword">
<!ENTITY filepicker_title "Choose File">

View File

@ -372,6 +372,7 @@ gbjar.sources += [
'Tab.java',
'Tabs.java',
'TabsAccessor.java',
'tabspanel/PrivateTabsPanel.java',
'tabspanel/RemoteTabsContainerPanel.java',
'tabspanel/RemoteTabsList.java',
'tabspanel/RemoteTabsPanel.java',

View File

@ -40,13 +40,11 @@
android:visibility="gone"
gecko:tabs="tabs_normal"/>
<org.mozilla.gecko.tabspanel.TabsTray android:id="@+id/private_tabs"
style="@style/TabsList"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:choiceMode="singleChoice"
android:visibility="gone"
gecko:tabs="tabs_private"/>
<org.mozilla.gecko.tabspanel.PrivateTabsPanel
android:id="@+id/private_tabs_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
<org.mozilla.gecko.tabspanel.RemoteTabsPanel
android:id="@+id/remote_tabs"

View File

@ -0,0 +1,50 @@
<?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/. -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto">
<org.mozilla.gecko.tabspanel.TabsTray android:id="@+id/private_tabs_tray"
style="@style/TabsList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
gecko:tabs="tabs_private"/>
<LinearLayout android:id="@+id/private_tabs_empty"
style="@style/TabsPanelFrame.PrivateTabs"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout style="@style/TabsPanelSection.PrivateTabs.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView style="@style/TabsPanelItem.TextAppearance.Header.PrivateTabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/private_browsing_title"/>
<TextView style="@style/TabsPanelItem.TextAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/private_tabs_panel_description"/>
</LinearLayout>
<LinearLayout style="@style/TabsPanelSection.PrivateTabs"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="@+id/private_tabs_learn_more"
style="@style/TabsPanelItem.TextAppearance.Linkified.LearnMore"
android:layout_width="match_parent"
android:text="@string/private_tabs_panel_learn_more"/>
</LinearLayout>
</LinearLayout>
</merge>

View File

@ -10,27 +10,27 @@
android:visibility="gone">
<LinearLayout android:id="@+id/remote_tabs_setup_containing_layout"
style="@style/RemoteTabsPanelChild"
style="@style/TabsPanelFrame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout style="@style/RemoteTabsSection"
<LinearLayout style="@style/TabsPanelSection"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView style="@style/RemoteTabsItem.TextAppearance.Header"
<TextView style="@style/TabsPanelItem.TextAppearance.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/fxaccount_getting_started_welcome_to_sync"/>
<TextView style="@style/RemoteTabsItem.TextAppearance"
<TextView style="@style/TabsPanelItem.TextAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/fxaccount_getting_started_description"/>
</LinearLayout>
<LinearLayout style="@style/RemoteTabsSection"
<LinearLayout style="@style/TabsPanelSection"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -40,7 +40,7 @@
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/RemoteTabsItem.Button"
style="@style/TabsPanelItem.Button"
android:text="@string/fxaccount_getting_started_get_started"
android:layout_marginBottom="15dp"/>
@ -49,7 +49,7 @@
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/RemoteTabsItem.TextAppearance.Linkified"
style="@style/TabsPanelItem.TextAppearance.Linkified"
android:text="@string/fxaccount_getting_started_old_firefox"/>
</LinearLayout>

View File

@ -10,40 +10,40 @@
android:visibility="gone">
<LinearLayout android:id="@+id/remote_tabs_verification_containing_layout"
style="@style/RemoteTabsPanelChild"
style="@style/TabsPanelFrame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout style="@style/RemoteTabsSection"
<LinearLayout style="@style/TabsPanelSection"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView style="@style/RemoteTabsItem.TextAppearance.Header.FXAccounts"
<TextView style="@style/TabsPanelItem.TextAppearance.Header.FXAccounts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/fxaccount_full_label"
android:layout_marginBottom="0dp"/>
<TextView style="@style/RemoteTabsItem.TextAppearance.Header"
<TextView style="@style/TabsPanelItem.TextAppearance.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/fxaccount_confirm_account_header"
android:layout_marginTop="0dp"/>
<TextView android:id="@+id/remote_tabs_confirm_verification"
style="@style/RemoteTabsItem.TextAppearance"
style="@style/TabsPanelItem.TextAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/fxaccount_confirm_account_verification_link"/>
</LinearLayout>
<LinearLayout style="@style/RemoteTabsSection"
<LinearLayout style="@style/TabsPanelSection"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="@+id/remote_tabs_confirm_resend"
style="@style/RemoteTabsItem.TextAppearance.Linkified.Resend"
style="@style/TabsPanelItem.TextAppearance.Linkified.Resend"
android:layout_width="match_parent"
android:text="@string/fxaccount_confirm_account_resend_email"/>

View File

@ -39,13 +39,11 @@
android:visibility="gone"
gecko:tabs="tabs_normal"/>
<org.mozilla.gecko.tabspanel.TabsTray android:id="@+id/private_tabs"
style="@style/TabsList"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:choiceMode="singleChoice"
android:visibility="gone"
gecko:tabs="tabs_private"/>
<org.mozilla.gecko.tabspanel.PrivateTabsPanel
android:id="@+id/private_tabs_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
<org.mozilla.gecko.tabspanel.RemoteTabsPanel
android:id="@+id/remote_tabs"

View File

@ -17,23 +17,31 @@
<item name="android:nextFocusUp">@+id/info</item>
</style>
<!-- Remote tabs panel -->
<style name="RemoteTabsPanelChild" parent="RemoteTabsPanelChildBase">
<!-- Tabs panel -->
<style name="TabsPanelFrame" parent="TabsPanelFrameBase">
<item name="android:orientation">horizontal</item>
<item name="android:paddingTop">24dp</item>
</style>
<style name="RemoteTabsSection" parent="RemoteTabsSectionBase">
<style name="TabsPanelFrame.PrivateTabs">
<item name="android:paddingTop">0dp</item>
</style>
<style name="TabsPanelSection" parent="TabsPanelSectionBase">
<item name="android:layout_weight">1</item>
</style>
<style name="RemoteTabsItem">
<style name="TabsPanelSection.PrivateTabs.Header">
<item name="android:paddingTop">18dp</item>
</style>
<style name="TabsPanelItem">
<item name="android:layout_marginBottom">20dp</item>
<item name="android:layout_gravity">left</item>
<item name="android:gravity">left</item>
</style>
<style name="RemoteTabsItem.Button" parent="RemoteTabsItem.ButtonBase">
<style name="TabsPanelItem.Button" parent="TabsPanelItem.ButtonBase">
<item name="android:paddingTop">12dp</item>
<item name="android:paddingBottom">12dp</item>
<item name="android:paddingLeft">6dp</item>
@ -42,11 +50,21 @@
<item name="android:layout_marginRight">24dp</item>
</style>
<style name="RemoteTabsItem.TextAppearance.Header.FXAccounts">
<style name="TabsPanelItem.TextAppearance.Header.FXAccounts">
<item name="android:visibility">gone</item>
</style>
<style name="RemoteTabsItem.TextAppearance.Linkified.Resend">
<style name="TabsPanelItem.TextAppearance.Header.PrivateTabs">
<item name="android:visibility">gone</item>
</style>
<style name="TabsPanelItem.TextAppearance.Linkified.LearnMore">
<item name="android:layout_height">match_parent</item>
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center</item>
</style>
<style name="TabsPanelItem.TextAppearance.Linkified.Resend">
<item name="android:layout_height">match_parent</item>
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center</item>

View File

@ -51,23 +51,36 @@
<item name="android:paddingTop">30dp</item>
</style>
<!-- Remote tabs panel -->
<style name="RemoteTabsPanelChild" parent="RemoteTabsPanelChildBase">
<!-- Tabs panel -->
<style name="TabsPanelFrame" parent="TabsPanelFrameBase">
<item name="android:paddingTop">32dp</item>
<!-- Additional spacing set via margins on RemoteTabsSection. -->
<!-- Additional spacing set via margins on TabsPanelSection. -->
<item name="android:paddingLeft">0dp</item>
<item name="android:paddingRight">0dp</item>
</style>
<style name="RemoteTabsSection" parent="RemoteTabsSectionBase">
<style name="TabsPanelFrame.PrivateTabs">
<!-- We set values in tablet portrait. -->
</style>
<style name="TabsPanelSection" parent="TabsPanelSectionBase">
<!-- To override the values-land style. -->
</style>
<style name="RemoteTabsItem" parent="RemoteTabsItemBase">
<style name="TabsPanelSection.PrivateTabs">
<!-- We set values in tablet portrait. -->
</style>
<style name="TabsPanelSection.PrivateTabs.Header">
<!-- We set values in tablet portrait. -->
</style>
<style name="TabsPanelItem" parent="TabsPanelItemBase">
<!-- To override the values-land style. -->
</style>
<style name="RemoteTabsItem.Button" parent="RemoteTabsItem.ButtonBase">
<style name="TabsPanelItem.Button" parent="TabsPanelItem.ButtonBase">
<item name="android:paddingTop">12dp</item>
<item name="android:paddingBottom">12dp</item>
<item name="android:paddingLeft">6dp</item>
@ -77,11 +90,19 @@
<item name="android:textSize">16dp</item>
</style>
<style name="RemoteTabsItem.TextAppearance.Header.FXAccounts">
<style name="TabsPanelItem.TextAppearance.Header.FXAccounts">
<item name="android:visibility">gone</item>
</style>
<style name="RemoteTabsItem.TextAppearance.Linkified.Resend">
<style name="TabsPanelItem.TextAppearance.Header.PrivateTabs">
<item name="android:visibility">visible</item>
</style>
<style name="TabsPanelItem.TextAppearance.Linkified.LearnMore">
<item name="android:layout_height">wrap_content</item>
</style>
<style name="TabsPanelItem.TextAppearance.Linkified.Resend">
<item name="android:layout_height">wrap_content</item>
</style>

View File

@ -94,12 +94,36 @@
</style>
<!-- Tabs panel -->
<style name="RemoteTabsPanelChild" parent="RemoteTabsPanelChildBase">
<style name="TabsPanelFrame.PrivateTabs">
<item name="android:orientation">horizontal</item>
<item name="android:paddingTop">0dp</item>
</style>
<style name="TabsPanelSection.PrivateTabs">
<item name="android:layout_weight">1</item>
</style>
<style name="TabsPanelSection.PrivateTabs.Header">
<item name="android:paddingTop">32dp</item>
</style>
<style name="TabsPanelItem.TextAppearance.Header.PrivateTabs">
<item name="android:visibility">gone</item>
</style>
<style name="TabsPanelItem.TextAppearance.Linkified.LearnMore">
<item name="android:layout_height">match_parent</item>
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center</item>
</style>
<!-- Tabs panel -->
<style name="TabsPanelFrame" parent="TabsPanelFrameBase">
<item name="android:paddingLeft">84dp</item>
<item name="android:paddingRight">84dp</item>
</style>
<style name="RemoteTabsItem.Button" parent="RemoteTabsItem.ButtonBase">
<style name="TabsPanelItem.Button" parent="TabsPanelItem.ButtonBase">
<item name="android:paddingTop">18dp</item>
<item name="android:paddingBottom">18dp</item>
<item name="android:paddingLeft">9dp</item>

View File

@ -29,7 +29,7 @@
</style>
<!-- Tabs panel -->
<style name="RemoteTabsItem.Button" parent="RemoteTabsItem.ButtonBase">
<style name="TabsPanelItem.Button" parent="TabsPanelItem.ButtonBase">
<item name="android:paddingTop">12dp</item>
<item name="android:paddingBottom">12dp</item>
<item name="android:paddingLeft">6dp</item>

View File

@ -31,7 +31,7 @@
</style>
<!-- Tabs panel -->
<style name="RemoteTabsItem.Button" parent="RemoteTabsItem.ButtonBase">
<style name="TabsPanelItem.Button" parent="TabsPanelItem.ButtonBase">
<item name="android:paddingTop">18dp</item>
<item name="android:paddingBottom">18dp</item>
<item name="android:paddingLeft">9dp</item>

View File

@ -448,46 +448,58 @@
<item name="android:groupIndicator">@android:color/transparent</item>
</style>
<!-- Remote tabs panel -->
<style name="RemoteTabsPanelChildBase">
<!-- Tabs panel -->
<style name="TabsPanelFrameBase">
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">16dp</item>
<item name="android:paddingTop">32dp</item>
<item name="android:orientation">vertical</item>
</style>
<style name="RemoteTabsPanelChild" parent="RemoteTabsPanelChildBase">
<style name="TabsPanelFrame" parent="TabsPanelFrameBase">
<!-- We set values in landscape. -->
</style>
<style name="RemoteTabsSectionBase">
<style name="TabsPanelFrame.PrivateTabs">
<!-- We set values on tablet. -->
</style>
<style name="TabsPanelSectionBase">
<item name="android:orientation">vertical</item>
<item name="android:layout_marginLeft">16dp</item>
<item name="android:layout_marginRight">16dp</item>
</style>
<style name="RemoteTabsSection" parent="RemoteTabsSectionBase">
<style name="TabsPanelSection" parent="TabsPanelSectionBase">
<!-- We set values in landscape. -->
</style>
<style name="RemoteTabsItemBase">
<style name="TabsPanelSection.PrivateTabs">
<!-- We set values on tablet. -->
</style>
<style name="TabsPanelSection.PrivateTabs.Header">
<!-- We set values on landscape and tablet. -->
</style>
<style name="TabsPanelItemBase">
<item name="android:layout_marginBottom">28dp</item>
<item name="android:layout_gravity">center</item>
<item name="android:gravity">center</item>
</style>
<style name="RemoteTabsItem" parent="RemoteTabsItemBase">
<style name="TabsPanelItem" parent="TabsPanelItemBase">
<!-- We set values in landscape. -->
</style>
<style name="RemoteTabsItem.ButtonBase">
<style name="TabsPanelItem.ButtonBase">
<item name="android:background">@drawable/remote_tabs_setup_button_background</item>
<item name="android:textColor">#FFFEFF</item>
<item name="android:textSize">20sp</item>
<item name="android:gravity">center</item>
</style>
<style name="RemoteTabsItem.Button" parent="RemoteTabsItem.ButtonBase">
<style name="TabsPanelItem.Button" parent="TabsPanelItem.ButtonBase">
<item name="android:paddingTop">18dp</item>
<item name="android:paddingBottom">18dp</item>
<item name="android:paddingLeft">9dp</item>
@ -496,26 +508,34 @@
<item name="android:layout_marginRight">24dp</item>
</style>
<style name="RemoteTabsItem.TextAppearance">
<style name="TabsPanelItem.TextAppearance">
<item name="android:textColor">#C0C9D0</item>
<item name="android:textSize">16sp</item>
</style>
<style name="RemoteTabsItem.TextAppearance.Header">
<style name="TabsPanelItem.TextAppearance.Header">
<item name="android:textSize">20sp</item>
</style>
<style name="RemoteTabsItem.TextAppearance.Header.FXAccounts">
<style name="TabsPanelItem.TextAppearance.Header.FXAccounts">
<!-- We change these values on landscape. -->
</style>
<style name="RemoteTabsItem.TextAppearance.Linkified">
<style name="TabsPanelItem.TextAppearance.Header.PrivateTabs">
<!-- We change these values on landscape and tablet. -->
</style>
<style name="TabsPanelItem.TextAppearance.Linkified">
<item name="android:clickable">true</item>
<item name="android:focusable">true</item>
<item name="android:textColor">#0292D6</item>
</style>
<style name="RemoteTabsItem.TextAppearance.Linkified.Resend">
<style name="TabsPanelItem.TextAppearance.Linkified.LearnMore">
<item name="android:layout_height">wrap_content</item>
</style>
<style name="TabsPanelItem.TextAppearance.Linkified.Resend">
<item name="android:layout_height">wrap_content</item>
</style>

View File

@ -322,6 +322,11 @@
<string name="home_reading_list_hint_accessible">&home_reading_list_hint_accessible;</string>
<string name="home_default_empty">&home_default_empty;</string>
<string name="home_move_up_to_filter">&home_move_up_to_filter;</string>
<string name="private_browsing_title">&private_browsing_title;</string>
<string name="private_tabs_panel_description">&private_tabs_panel_description;</string>
<string name="private_tabs_panel_learn_more">&private_tabs_panel_learn_more;</string>
<!-- https://support.mozilla.org/%LOCALE%/kb/mobile-private-browsing-browse-web-without-saving-syncing-info -->
<string name="private_tabs_panel_learn_more_link">https://support.mozilla.org/&formatS1;/kb/mobile-private-browsing-browse-web-without-saving-syncing-info</string>
<string name="pin_site_dialog_hint">&pin_site_dialog_hint;</string>
<string name="filepicker_title">&filepicker_title;</string>

View File

@ -32,6 +32,11 @@ public class SyncAccountDeletedService extends IntentService {
@Override
protected void onHandleIntent(Intent intent) {
// Intent can, in theory, be null. Bug 1025937.
if (intent == null) {
Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
}
final Context context = this;
long intentVersion = intent.getLongExtra(Constants.JSON_KEY_VERSION, 0);

View File

@ -0,0 +1,77 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.tabspanel;
import java.util.Locale;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.tabspanel.TabsPanel.PanelView;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
/**
* A container that wraps the private tabs {@link android.widget.AdapterView} and empty
* {@link android.view.View} to manage both of their visibility states by changing the visibility of
* this container as calling {@link android.widget.AdapterView#setVisibility} does not affect the
* empty View's visibility.
*/
class PrivateTabsPanel extends FrameLayout implements PanelView {
private TabsPanel tabsPanel;
private TabsTray tabsTray;
public PrivateTabsPanel(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.private_tabs_panel, this);
tabsTray = (TabsTray) findViewById(R.id.private_tabs_tray);
final View emptyView = findViewById(R.id.private_tabs_empty);
tabsTray.setEmptyView(emptyView);
final View learnMore = findViewById(R.id.private_tabs_learn_more);
learnMore.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final String locale = BrowserLocaleManager.getLanguageTag(Locale.getDefault());
final String url =
getResources().getString(R.string.private_tabs_panel_learn_more_link, locale);
Tabs.getInstance().loadUrlInTab(url);
if (tabsPanel != null) {
tabsPanel.autoHidePanel();
}
}
});
}
@Override
public void setTabsPanel(TabsPanel panel) {
tabsPanel = panel;
tabsTray.setTabsPanel(panel);
}
@Override
public void show() {
tabsTray.show();
setVisibility(View.VISIBLE);
}
@Override
public void hide() {
setVisibility(View.GONE);
tabsTray.hide();
}
@Override
public boolean shouldExpand() {
return tabsTray.shouldExpand();
}
}

View File

@ -120,7 +120,7 @@ public class TabsPanel extends LinearLayout
mPanelNormal = (PanelView) findViewById(R.id.normal_tabs);
mPanelNormal.setTabsPanel(this);
mPanelPrivate = (PanelView) findViewById(R.id.private_tabs);
mPanelPrivate = (PanelView) findViewById(R.id.private_tabs_panel);
mPanelPrivate.setTabsPanel(this);
mPanelRemote = (PanelView) findViewById(R.id.remote_tabs);

View File

@ -42,6 +42,7 @@ public class ButtonToast {
public enum ReasonHidden {
CLICKED,
TOUCH_OUTSIDE,
TIMEOUT,
REPLACED,
STARTUP
@ -129,6 +130,11 @@ public class ButtonToast {
}
public void hide(boolean immediate, ReasonHidden reason) {
// There's nothing to do if the view is already hidden.
if (mView.getVisibility() == View.GONE) {
return;
}
if (mCurrentToast != null && mCurrentToast.listener != null) {
mCurrentToast.listener.onToastHidden(reason);
}

View File

@ -13,6 +13,18 @@ var rokuTarget = {
}
};
var fireflyTarget = {
target: "urn:dial-multiscreen-org:service:dial:1",
filters: {
server: null,
modelName: "Eureka Dongle"
},
factory: function(aService) {
Cu.import("resource://gre/modules/FireflyApp.jsm");
return new FireflyApp(aService);
}
};
var CastingApps = {
_castMenuId: -1,
@ -23,6 +35,7 @@ var CastingApps = {
// Register targets
SimpleServiceDiscovery.registerTarget(rokuTarget);
SimpleServiceDiscovery.registerTarget(fireflyTarget);
// Search for devices continuously every 120 seconds
SimpleServiceDiscovery.search(120 * 1000);
@ -250,8 +263,7 @@ var CastingApps = {
// Look for a castable <video> that is playing, and start casting it
let videos = browser.contentDocument.querySelectorAll("video");
for (let video of videos) {
let unwrappedVideo = XPCNativeWrapper.unwrap(video);
if (!video.paused && unwrappedVideo.mozAllowCasting) {
if (!video.paused && video.mozAllowCasting) {
UITelemetry.addEvent("cast.1", "pageaction", null);
CastingApps.openExternal(video, 0, 0);
return;
@ -270,13 +282,12 @@ var CastingApps = {
let castableVideo = null;
let videos = aBrowser.contentDocument.querySelectorAll("video");
for (let video of videos) {
let unwrappedVideo = XPCNativeWrapper.unwrap(video);
if (unwrappedVideo.mozIsCasting) {
if (video.mozIsCasting) {
// This <video> is cast-active. Break out of loop.
return video;
}
if (!video.paused && unwrappedVideo.mozAllowCasting) {
if (!video.paused && video.mozAllowCasting) {
// This <video> is cast-ready. Keep looking so cast-active could be found.
castableVideo = video;
}
@ -324,15 +335,14 @@ var CastingApps = {
// 1. The video is actively being cast
// 2. The video is allowed to be cast and is currently playing
// Both states have the same action: Show the cast page action
let unwrappedVideo = XPCNativeWrapper.unwrap(aVideo);
if (unwrappedVideo.mozIsCasting) {
if (aVideo.mozIsCasting) {
this.pageAction.id = NativeWindow.pageactions.add({
title: Strings.browser.GetStringFromName("contextmenu.castToScreen"),
icon: "drawable://casting_active",
clickCallback: this.pageAction.click,
important: true
});
} else if (unwrappedVideo.mozAllowCasting) {
} else if (aVideo.mozAllowCasting) {
this.pageAction.id = NativeWindow.pageactions.add({
title: Strings.browser.GetStringFromName("contextmenu.castToScreen"),
icon: "drawable://casting",
@ -363,6 +373,7 @@ var CastingApps = {
},
handleContextMenu: function(aElement, aX, aY) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_cast");
UITelemetry.addEvent("cast.1", "contextmenu", null);
this.openExternal(aElement, aX, aY);
},

View File

@ -482,6 +482,9 @@ var BrowserApp = {
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.openInNewTab"),
NativeWindow.contextmenus.linkOpenableNonPrivateContext,
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_open_new_tab");
UITelemetry.addEvent("loadurl.1", "contextmenu", null);
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal);
let tab = BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id });
@ -501,6 +504,9 @@ var BrowserApp = {
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.openInPrivateTab"),
NativeWindow.contextmenus.linkOpenableContext,
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_open_private_tab");
UITelemetry.addEvent("loadurl.1", "contextmenu", null);
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal);
let tab = BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id, isPrivate: true });
@ -520,6 +526,8 @@ var BrowserApp = {
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copyLink"),
NativeWindow.contextmenus.linkCopyableContext,
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_link");
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
NativeWindow.contextmenus._copyStringToDefaultClipboard(url);
});
@ -527,6 +535,8 @@ var BrowserApp = {
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copyEmailAddress"),
NativeWindow.contextmenus.emailLinkContext,
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_email");
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
let emailAddr = NativeWindow.contextmenus._stripScheme(url);
NativeWindow.contextmenus._copyStringToDefaultClipboard(emailAddr);
@ -535,6 +545,8 @@ var BrowserApp = {
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copyPhoneNumber"),
NativeWindow.contextmenus.phoneNumberLinkContext,
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_phone");
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
let phoneNumber = NativeWindow.contextmenus._stripScheme(url);
NativeWindow.contextmenus._copyStringToDefaultClipboard(phoneNumber);
@ -551,7 +563,9 @@ var BrowserApp = {
};
},
icon: "drawable://ic_menu_share",
callback: function(aTarget) { }
callback: function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_link");
}
});
NativeWindow.contextmenus.add({
@ -568,7 +582,9 @@ var BrowserApp = {
};
},
icon: "drawable://ic_menu_share",
callback: function(aTarget) { }
callback: function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_email");
}
});
NativeWindow.contextmenus.add({
@ -585,12 +601,16 @@ var BrowserApp = {
};
},
icon: "drawable://ic_menu_share",
callback: function(aTarget) { }
callback: function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_phone");
}
});
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.addToContacts"),
NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.emailLinkContext),
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_contact_email");
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
sendMessageToJava({
type: "Contact:Add",
@ -601,6 +621,8 @@ var BrowserApp = {
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.addToContacts"),
NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.phoneNumberLinkContext),
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_contact_phone");
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
sendMessageToJava({
type: "Contact:Add",
@ -611,6 +633,8 @@ var BrowserApp = {
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.bookmarkLink"),
NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.linkBookmarkableContext),
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_bookmark");
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
let title = aTarget.textContent || aTarget.title || url;
sendMessageToJava({
@ -623,18 +647,21 @@ var BrowserApp = {
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.playMedia"),
NativeWindow.contextmenus.mediaContext("media-paused"),
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_play");
aTarget.play();
});
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.pauseMedia"),
NativeWindow.contextmenus.mediaContext("media-playing"),
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_pause");
aTarget.pause();
});
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.showControls2"),
NativeWindow.contextmenus.mediaContext("media-hidingcontrols"),
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_controls_media");
aTarget.setAttribute("controls", true);
});
@ -653,30 +680,36 @@ var BrowserApp = {
},
icon: "drawable://ic_menu_share",
callback: function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_media");
}
});
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.fullScreen"),
NativeWindow.contextmenus.SelectorContext("video:not(:-moz-full-screen)"),
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_fullscreen");
aTarget.mozRequestFullScreen();
});
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.mute"),
NativeWindow.contextmenus.mediaContext("media-unmuted"),
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_mute");
aTarget.muted = true;
});
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.unmute"),
NativeWindow.contextmenus.mediaContext("media-muted"),
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_unmute");
aTarget.muted = false;
});
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copyImageLocation"),
NativeWindow.contextmenus.imageLocationCopyableContext,
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_image");
let url = aTarget.src;
NativeWindow.contextmenus._copyStringToDefaultClipboard(url);
});
@ -699,12 +732,16 @@ var BrowserApp = {
},
icon: "drawable://ic_menu_share",
menu: true,
callback: function(aTarget) { }
callback: function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_image");
}
});
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.saveImage"),
NativeWindow.contextmenus.imageSaveableContext,
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_save_image");
ContentAreaUtils.saveImageURL(aTarget.currentURI.spec, null, "SaveImageTitle",
false, true, aTarget.ownerDocument.documentURIObject,
aTarget.ownerDocument);
@ -713,6 +750,8 @@ var BrowserApp = {
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.setImageAs"),
NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.imageSaveableContext),
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_background_image");
let src = aTarget.src;
sendMessageToJava({
type: "Image:SetAs",
@ -734,6 +773,8 @@ var BrowserApp = {
return Strings.browser.GetStringFromName("contextmenu.saveVideo");
}, NativeWindow.contextmenus.mediaSaveableContext,
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_save_media");
let url = aTarget.currentSrc || aTarget.src;
let filePickerTitleKey = (aTarget instanceof HTMLVideoElement &&
(aTarget.videoWidth != 0 && aTarget.videoHeight != 0))

View File

@ -0,0 +1,318 @@
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["FireflyApp"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
// meta constants
const HEADER_META = ".meta";
const PROTO_CMD_KEY = "cmd";
const PROTO_CMD_VALUE_CREATE_SESSION = "create-session";
const PROTO_CMD_VALUE_END_SESSION = "end-session";
const PROTO_CMD_VALUE_ATTACH_APP = "attach-app";
const PROTO_CMD_VALUE_SET_WIFI = "set-wifi";
const PROTO_CMD_RESPONSE_PREFIX = "~";
const PROTO_CMD_VALUE_START_APP = "start-app";
const PROTO_CMD_VALUE_CANCEL_START_APP = "cancel-start-app";
const PROTO_CMD_VALUE_DOWNLODING_APP = "downloading-app";
const PROTO_CMD_VALUE_ATTACH_APP_MANAGER = "attach-app-manager";
const PROTO_CMD_VALUE_EXCEPTION = "exception";
const PROTO_EXCEPTION_KEY = "exception";
const PROTO_EXCEPTION_NO_KEY = "errno";
const PROTO_STATUS_VALUE_APP_CLOSED = "app-closed";
const PROTO_STATUS_VALUE_TRY_LATER = "try-later";
const PROTO_STATUS_VALUE_CANCELLED = "cancelled";
const PROTO_STATUS_VALUE_REFUSED = "refused";
const PROTO_STATUS_VALUE_TIMEOUT = "time-out";
const PROTO_STATUS_VALUE_INVALID_APP = "app_not_exist";
const PROTO_STATUS_VALUE_APP_CHECK_FAIL = "app_check_fail";
const PROTO_STATUS_VALUE_APP_INSTALL_FAIL = "app_install_fail";
const PROTO_STATUS_VALUE_APP_DOWNLOAD_FAIL = "app_download_fail";
const PROTO_STATUS_VALUE_APP_LAUNCH_FAIL = "app_launch_fail";
const PROTO_STATUS_KEY = "status";
const PROTO_STATUS_VALUE_OK = "OK";
const PROTO_STATUS_VALUE_FAILED = "FAILED_REASON";
const PROTO_EXCEPTION_NO_APP_NAME_NULL = 10;
const PROTO_EXCEPTION_NO_MESSAGE_NULL = 11;
const PROTO_EXCEPTION_NO_NEW_GROUP_START = 12;
const PROTO_EXCEPTION_NO_UNKNOW_CMD = 13;
const PROTO_EXCEPTION_NO_JSON_DATA_ERROR = 14;
const PARAM_APP_NAME = "app-name";
const PROTO_HOME_APP = "home";
const PARAM_DOWNLOADING_PERCENT = "download-percent";
const PARAM_KEEP_SESSION = "keep-session";
const PROTO_SENDER_HOST_NAME_KEY = "sender-host-name";
const PROTO_SENDER_PORT_KEY = "sender-port";
const PROTO_APP_META_KEY = "app-meta";
const PROTO_APP_META_NAME = "name";
const PROTO_APP_META_TITLE = "title";
const PROTO_APP_META_IMG_URI = "img-uri";
const PROTO_MIME_DATA_KEY = "mime-data";
const PROTO_APP_NAME_KEY = "app-name";
const PARAM_WIFI_NAME = "wifi-name";
const PARAM_WIFI_PASSWORD = "wifi-password";
const PARAM_WIFI_TYPE = "wifi-type";
const PARAM_WIFI_KEY = "key";
const PROTO_MIME_DATA_KEY_TYPE = "type";
const PROTO_MIME_DATA_KEY_DATA = "data";
// RAMP constants
const RAMP_CMD_KEY_ID = "cmd_id";
const RAMP_CMD_KEY_TYPE = "type";
const RAMP_CMD_KEY_STATUS = "status";
const RAMP_CMD_KEY_URL = "url";
const RAMP_CMD_KEY_VALUE = "value";
const RAMP_CMD_KEY_VIDEO_NAME = "videoname";
const RAMP_CMD_KEY_EVENT_SEQ = "event_sequence";
const NAMESPACE_RAMP = "ramp";
const RAMP_CMD_ID_START = 0;
const RAMP_CMD_ID_INFO = 1;
const RAMP_CMD_ID_PLAY = 2;
const RAMP_CMD_ID_PAUSE = 3;
const RAMP_CMD_ID_STOP = 4;
const RAMP_CMD_ID_SEEKTO = 5;
const RAMP_CMD_ID_SETVOLUME = 6;
const RAMP_CMD_ID_MUTE = 7;
const RAMP_CMD_ID_DECONSTE = 8;
const RAMP_CMD_START = "START";
const RAMP_CMD_INFO = "INFO";
const RAMP_CMD_PLAY = "PLAY";
const RAMP_CMD_PAUSE = "PAUSE";
const RAMP_CMD_STOP = "STOP";
const RAMP_CMD_SEEKTO = "SEEKTO";
const RAMP_CMD_SETVOLUME = "SETVOLUME";
const RAMP_CMD_MUTE = "MUTE";
const RAMP_CMD_DECONSTE = "DECONSTE";
const RAMP_CAST_STATUS_EVENT_SEQUENCE = "event_sequence";
const RAMP_CAST_STATUS_STATE = "state";
const RAMP_CAST_STATUS_CONTENT_ID = "content_id";
const RAMP_CAST_STATUS_CURRENT_TIME = "current_time";
const RAMP_CAST_STATUS_DURATION = "duration";
const RAMP_CAST_STATUS_MUTED = "muted";
const RAMP_CAST_STATUS_TIME_PROGRESS = "time_progress";
const RAMP_CAST_STATUS_TITLE = "title";
const RAMP_CAST_STATUS_VOLUME = "volume";
const PLAYER_STATUS_PREPARING = 1;
const PLAYER_STATUS_PLAYING = 2;
const PLAYER_STATUS_PAUSE = 3;
const PLAYER_STATUS_STOP = 4;
const PLAYER_STATUS_IDLE = 5;
const PLAYER_STATUS_BUFFERING = 6;
function FireflyApp(service) {
let uri = Services.io.newURI(service.location, null, null);
this._ip = uri.host;
};
FireflyApp.prototype = {
_ip: null,
_port: 8888,
_cmd_socket: null,
_meta_callback: null,
_ramp_callbacks: {},
_mediaListener: null,
_event_sequence: 0,
status: "unloaded",
_have_session: false,
_info_timer: null,
_send_meta_cmd: function(cmd, callback, extras) {
this._meta_callback = callback;
let msg = extras ? extras : {};
msg.cmd = cmd;
this._send_cmd(JSON.stringify([HEADER_META, msg]), 0);
},
_send_ramp_cmd: function(type, cmd_id, callback, extras) {
let msg = extras ? extras : {};
msg.cmd_id = cmd_id;
msg.type = type;
msg.event_sequence = this._event_sequence++;
this._send_cmd(JSON.stringify([NAMESPACE_RAMP, msg]), 0);
},
_send_cmd: function(str, recursionDepth) {
if (!this._cmd_socket) {
let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
this._cmd_socket = baseSocket.open(this._ip, 8888, { useSecureTransport: false, binaryType: "string" });
if (!(this._cmd_socket)) {
dump("socket is null");
return;
}
this._cmd_socket.ondata = function(response) {
try {
let data = JSON.parse(response.data);
let res = data[1];
switch (data[0]) {
case ".meta":
this._handle_meta_response(data[1]);
return;
case "ramp":
this._handle_ramp_response(data[1]);
return;
default:
dump("unknown response");
}
} catch(ex) {
dump("error handling response: " + ex);
if (!this._info_timer) {
this._info_timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._info_timer.init(this, 200, Ci.nsITimer.TYPE_ONE_SHOT);
}
}
}.bind(this);
this._cmd_socket.onerror = function(err) {
this.shutdown();
Cu.reportError("error: " + err.data.name);
this._cmd_socket = null;
}.bind(this);
this._cmd_socket.onclose = function() {
this.shutdown();
this._cmd_socket = null;
Cu.reportError("closed tcp socket")
}.bind(this);
this._cmd_socket.onopen = function() {
if (recursionDepth <= 2) {
this._send_cmd(str, ++recursionDepth);
}
}.bind(this);
} else {
try {
this._cmd_socket.send(str, str.length);
let data = JSON.parse(str);
if (data[1][PARAM_APP_NAME] == PROTO_HOME_APP) {
// assuming we got home OK
if (this._meta_callback) {
this._handle_meta_callback({ status: "OK" });
}
}
} catch (ex) {
this._cmd_socket = null;
this._send_cmd(str);
}
}
},
observe: function(subject, data, topic) {
if (data === "timer-callback") {
this._info_timer = null;
this._send_ramp_cmd(RAMP_CMD_INFO, RAMP_CMD_ID_INFO, null)
}
},
start: function(func) {
let cmd = this._have_session ? PROTO_CMD_VALUE_START_APP : PROTO_CMD_VALUE_CREATE_SESSION ;
this._send_meta_cmd(cmd, func, { "app-name": "Remote Player" });
},
stop: function(func) {
if (func) {
func(true);
}
},
remoteMedia: function(func, listener) {
this._mediaListener = listener;
func(this);
if (listener) {
listener.onRemoteMediaStart(this);
}
},
_handle_meta_response: function(data) {
switch(data.cmd) {
case "create-session":
case "~create-session":
// if we get a response form start-app, assume we have a connection already
case "start-app":
case "~start-app":
this._have_session = (data.status == "OK");
break;
case "end-session":
case "~end-session":
this._have_session = (data.status != "OK");
break;
}
if (this._meta_callback) {
let callback = this._meta_callback;
this._meta_callback = null;
callback(data.status == "OK");
}
},
_handle_ramp_response: function(data) {
switch (data.status.state) {
case PLAYER_STATUS_PREPARING:
this.status = "preparing";
break;
case PLAYER_STATUS_PLAYING:
this.status = "started";
break;
case PLAYER_STATUS_PAUSE:
this.status = "paused";
break;
case PLAYER_STATUS_STOP:
this.status = "stopped";
break;
case PLAYER_STATUS_IDLE:
this.status = "idle";
break;
case PLAYER_STATUS_BUFFERING:
this.status = "buffering";
break;
}
if (data.status.state == PLAYER_STATUS_STOP && data.status.current_time > 0 && data.status.current_time == data.status.duration) {
this.status = "completed";
} else if (!this._info_timer) {
this._info_timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._info_timer.init(this, 200, Ci.nsITimer.TYPE_ONE_SHOT);
}
if (this._mediaListener) {
this._mediaListener.onRemoteMediaStatus(this);
}
},
load: function(data) {
let meta = {
url: data.source,
videoname: data.title
};
this._send_ramp_cmd(RAMP_CMD_START, RAMP_CMD_ID_START, null, meta);
},
play: function() {
this._send_ramp_cmd(RAMP_CMD_PLAY, RAMP_CMD_ID_PLAY, null);
},
pause: function() {
this._send_ramp_cmd(RAMP_CMD_PAUSE, RAMP_CMD_ID_PAUSE, null);
},
shutdown: function() {
if (this._info_timer) {
this._info_timer.clear();
this._info_timer = null;
}
this.stop(function() {
this._send_meta_cmd(PROTO_CMD_VALUE_END_SESSION);
if (this._mediaListener) {
this._mediaListener.onRemoteMediaStop(this);
}
}.bind(this));
}
};

View File

@ -9,6 +9,7 @@ EXTRA_JS_MODULES += [
'AndroidLog.jsm',
'ContactService.jsm',
'dbg-browser-actors.js',
'FireflyApp.jsm',
'HelperApps.jsm',
'Home.jsm',
'HomeProvider.jsm',

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