.'
);
}
}
addAttr(el, name, JSON.stringify(value));
}
}
}
function checkInFor (el) {
var parent = el;
while (parent) {
if (parent.for !== undefined) {
return true
}
parent = parent.parent;
}
return false
}
function parseModifiers (name) {
var match = name.match(modifierRE);
if (match) {
var ret = {};
match.forEach(function (m) { ret[m.slice(1)] = true; });
return ret
}
}
function makeAttrsMap (attrs) {
var map = {};
for (var i = 0, l = attrs.length; i < l; i++) {
if (
"development" !== 'production' &&
map[attrs[i].name] && !isIE && !isEdge
) {
warn$2('duplicate attribute: ' + attrs[i].name);
}
map[attrs[i].name] = attrs[i].value;
}
return map
}
// for script (e.g. type="x/template") or style, do not decode content
function isTextTag (el) {
return el.tag === 'script' || el.tag === 'style'
}
function isForbiddenTag (el) {
return (
el.tag === 'style' ||
(el.tag === 'script' && (
!el.attrsMap.type ||
el.attrsMap.type === 'text/javascript'
))
)
}
var ieNSBug = /^xmlns:NS\d+/;
var ieNSPrefix = /^NS\d+:/;
/* istanbul ignore next */
function guardIESVGBug (attrs) {
var res = [];
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
if (!ieNSBug.test(attr.name)) {
attr.name = attr.name.replace(ieNSPrefix, '');
res.push(attr);
}
}
return res
}
function checkForAliasModel (el, value) {
var _el = el;
while (_el) {
if (_el.for && _el.alias === value) {
warn$2(
"<" + (el.tag) + " v-model=\"" + value + "\">: " +
"You are binding v-model directly to a v-for iteration alias. " +
"This will not be able to modify the v-for source array because " +
"writing to the alias is like modifying a function local variable. " +
"Consider using an array of objects and use v-model on an object property instead."
);
}
_el = _el.parent;
}
}
/* */
var isStaticKey;
var isPlatformReservedTag;
var genStaticKeysCached = cached(genStaticKeys$1);
/**
* Goal of the optimizer: walk the generated template AST tree
* and detect sub-trees that are purely static, i.e. parts of
* the DOM that never needs to change.
*
* Once we detect these sub-trees, we can:
*
* 1. Hoist them into constants, so that we no longer need to
* create fresh nodes for them on each re-render;
* 2. Completely skip them in the patching process.
*/
function optimize (root, options) {
if (!root) { return }
isStaticKey = genStaticKeysCached(options.staticKeys || '');
isPlatformReservedTag = options.isReservedTag || no;
// first pass: mark all non-static nodes.
markStatic$1(root);
// second pass: mark static roots.
markStaticRoots(root, false);
}
function genStaticKeys$1 (keys) {
return makeMap(
'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +
(keys ? ',' + keys : '')
)
}
function markStatic$1 (node) {
node.static = isStatic(node);
if (node.type === 1) {
// do not make component slot content static. this avoids
// 1. components not able to mutate slot nodes
// 2. static slot content fails for hot-reloading
if (
!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
return
}
for (var i = 0, l = node.children.length; i < l; i++) {
var child = node.children[i];
markStatic$1(child);
if (!child.static) {
node.static = false;
}
}
if (node.ifConditions) {
for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
var block = node.ifConditions[i$1].block;
markStatic$1(block);
if (!block.static) {
node.static = false;
}
}
}
}
}
function markStaticRoots (node, isInFor) {
if (node.type === 1) {
if (node.static || node.once) {
node.staticInFor = isInFor;
}
// For a node to qualify as a static root, it should have children that
// are not just static text. Otherwise the cost of hoisting out will
// outweigh the benefits and it's better off to just always render it fresh.
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true;
return
} else {
node.staticRoot = false;
}
if (node.children) {
for (var i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for);
}
}
if (node.ifConditions) {
for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
markStaticRoots(node.ifConditions[i$1].block, isInFor);
}
}
}
}
function isStatic (node) {
if (node.type === 2) { // expression
return false
}
if (node.type === 3) { // text
return true
}
return !!(node.pre || (
!node.hasBindings && // no dynamic bindings
!node.if && !node.for && // not v-if or v-for or v-else
!isBuiltInTag(node.tag) && // not a built-in
isPlatformReservedTag(node.tag) && // not a component
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}
function isDirectChildOfTemplateFor (node) {
while (node.parent) {
node = node.parent;
if (node.tag !== 'template') {
return false
}
if (node.for) {
return true
}
}
return false
}
/* */
var fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/;
var simplePathRE = /^\s*[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?']|\[".*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*\s*$/;
// keyCode aliases
var keyCodes = {
esc: 27,
tab: 9,
enter: 13,
space: 32,
up: 38,
left: 37,
right: 39,
down: 40,
'delete': [8, 46]
};
// #4868: modifiers that prevent the execution of the listener
// need to explicitly return null so that we can determine whether to remove
// the listener for .once
var genGuard = function (condition) { return ("if(" + condition + ")return null;"); };
var modifierCode = {
stop: '$event.stopPropagation();',
prevent: '$event.preventDefault();',
self: genGuard("$event.target !== $event.currentTarget"),
ctrl: genGuard("!$event.ctrlKey"),
shift: genGuard("!$event.shiftKey"),
alt: genGuard("!$event.altKey"),
meta: genGuard("!$event.metaKey"),
left: genGuard("'button' in $event && $event.button !== 0"),
middle: genGuard("'button' in $event && $event.button !== 1"),
right: genGuard("'button' in $event && $event.button !== 2")
};
function genHandlers (
events,
isNative,
warn
) {
var res = isNative ? 'nativeOn:{' : 'on:{';
for (var name in events) {
var handler = events[name];
// #5330: warn click.right, since right clicks do not actually fire click events.
if ("development" !== 'production' &&
name === 'click' &&
handler && handler.modifiers && handler.modifiers.right
) {
warn(
"Use \"contextmenu\" instead of \"click.right\" since right clicks " +
"do not actually fire \"click\" events."
);
}
res += "\"" + name + "\":" + (genHandler(name, handler)) + ",";
}
return res.slice(0, -1) + '}'
}
function genHandler (
name,
handler
) {
if (!handler) {
return 'function(){}'
}
if (Array.isArray(handler)) {
return ("[" + (handler.map(function (handler) { return genHandler(name, handler); }).join(',')) + "]")
}
var isMethodPath = simplePathRE.test(handler.value);
var isFunctionExpression = fnExpRE.test(handler.value);
if (!handler.modifiers) {
return isMethodPath || isFunctionExpression
? handler.value
: ("function($event){" + (handler.value) + "}") // inline statement
} else {
var code = '';
var genModifierCode = '';
var keys = [];
for (var key in handler.modifiers) {
if (modifierCode[key]) {
genModifierCode += modifierCode[key];
// left/right
if (keyCodes[key]) {
keys.push(key);
}
} else {
keys.push(key);
}
}
if (keys.length) {
code += genKeyFilter(keys);
}
// Make sure modifiers like prevent and stop get executed after key filtering
if (genModifierCode) {
code += genModifierCode;
}
var handlerCode = isMethodPath
? handler.value + '($event)'
: isFunctionExpression
? ("(" + (handler.value) + ")($event)")
: handler.value;
return ("function($event){" + code + handlerCode + "}")
}
}
function genKeyFilter (keys) {
return ("if(!('button' in $event)&&" + (keys.map(genFilterCode).join('&&')) + ")return null;")
}
function genFilterCode (key) {
var keyVal = parseInt(key, 10);
if (keyVal) {
return ("$event.keyCode!==" + keyVal)
}
var alias = keyCodes[key];
return ("_k($event.keyCode," + (JSON.stringify(key)) + (alias ? ',' + JSON.stringify(alias) : '') + ")")
}
/* */
function on (el, dir) {
if ("development" !== 'production' && dir.modifiers) {
warn("v-on without argument does not support modifiers.");
}
el.wrapListeners = function (code) { return ("_g(" + code + "," + (dir.value) + ")"); };
}
/* */
function bind$1 (el, dir) {
el.wrapData = function (code) {
return ("_b(" + code + ",'" + (el.tag) + "'," + (dir.value) + "," + (dir.modifiers && dir.modifiers.prop ? 'true' : 'false') + (dir.modifiers && dir.modifiers.sync ? ',true' : '') + ")")
};
}
/* */
var baseDirectives = {
on: on,
bind: bind$1,
cloak: noop
};
/* */
var CodegenState = function CodegenState (options) {
this.options = options;
this.warn = options.warn || baseWarn;
this.transforms = pluckModuleFunction(options.modules, 'transformCode');
this.dataGenFns = pluckModuleFunction(options.modules, 'genData');
this.directives = extend(extend({}, baseDirectives), options.directives);
var isReservedTag = options.isReservedTag || no;
this.maybeComponent = function (el) { return !isReservedTag(el.tag); };
this.onceId = 0;
this.staticRenderFns = [];
};
function generate (
ast,
options
) {
var state = new CodegenState(options);
var code = ast ? genElement(ast, state) : '_c("div")';
return {
render: ("with(this){return " + code + "}"),
staticRenderFns: state.staticRenderFns
}
}
function genElement (el, state) {
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {
// component or element
var code;
if (el.component) {
code = genComponent(el.component, el, state);
} else {
var data = el.plain ? undefined : genData$2(el, state);
var children = el.inlineTemplate ? null : genChildren(el, state, true);
code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
}
// module transforms
for (var i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code);
}
return code
}
}
// hoist static sub-trees out
function genStatic (el, state) {
el.staticProcessed = true;
state.staticRenderFns.push(("with(this){return " + (genElement(el, state)) + "}"));
return ("_m(" + (state.staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")")
}
// v-once
function genOnce (el, state) {
el.onceProcessed = true;
if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.staticInFor) {
var key = '';
var parent = el.parent;
while (parent) {
if (parent.for) {
key = parent.key;
break
}
parent = parent.parent;
}
if (!key) {
"development" !== 'production' && state.warn(
"v-once can only be used inside v-for that is keyed. "
);
return genElement(el, state)
}
return ("_o(" + (genElement(el, state)) + "," + (state.onceId++) + "," + key + ")")
} else {
return genStatic(el, state)
}
}
function genIf (
el,
state,
altGen,
altEmpty
) {
el.ifProcessed = true; // avoid recursion
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
function genIfConditions (
conditions,
state,
altGen,
altEmpty
) {
if (!conditions.length) {
return altEmpty || '_e()'
}
var condition = conditions.shift();
if (condition.exp) {
return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty)))
} else {
return ("" + (genTernaryExp(condition.block)))
}
// v-if with v-once should generate code like (a)?_m(0):_m(1)
function genTernaryExp (el) {
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)
}
}
function genFor (
el,
state,
altGen,
altHelper
) {
var exp = el.for;
var alias = el.alias;
var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : '';
var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : '';
if ("development" !== 'production' &&
state.maybeComponent(el) &&
el.tag !== 'slot' &&
el.tag !== 'template' &&
!el.key
) {
state.warn(
"<" + (el.tag) + " v-for=\"" + alias + " in " + exp + "\">: component lists rendered with " +
"v-for should have explicit keys. " +
"See https://vuejs.org/guide/list.html#key for more info.",
true /* tip */
);
}
el.forProcessed = true; // avoid recursion
return (altHelper || '_l') + "((" + exp + ")," +
"function(" + alias + iterator1 + iterator2 + "){" +
"return " + ((altGen || genElement)(el, state)) +
'})'
}
function genData$2 (el, state) {
var data = '{';
// directives first.
// directives may mutate the el's other properties before they are generated.
var dirs = genDirectives(el, state);
if (dirs) { data += dirs + ','; }
// key
if (el.key) {
data += "key:" + (el.key) + ",";
}
// ref
if (el.ref) {
data += "ref:" + (el.ref) + ",";
}
if (el.refInFor) {
data += "refInFor:true,";
}
// pre
if (el.pre) {
data += "pre:true,";
}
// record original tag name for components using "is" attribute
if (el.component) {
data += "tag:\"" + (el.tag) + "\",";
}
// module data generation functions
for (var i = 0; i < state.dataGenFns.length; i++) {
data += state.dataGenFns[i](el);
}
// attributes
if (el.attrs) {
data += "attrs:{" + (genProps(el.attrs)) + "},";
}
// DOM props
if (el.props) {
data += "domProps:{" + (genProps(el.props)) + "},";
}
// event handlers
if (el.events) {
data += (genHandlers(el.events, false, state.warn)) + ",";
}
if (el.nativeEvents) {
data += (genHandlers(el.nativeEvents, true, state.warn)) + ",";
}
// slot target
if (el.slotTarget) {
data += "slot:" + (el.slotTarget) + ",";
}
// scoped slots
if (el.scopedSlots) {
data += (genScopedSlots(el.scopedSlots, state)) + ",";
}
// component v-model
if (el.model) {
data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
}
// inline-template
if (el.inlineTemplate) {
var inlineTemplate = genInlineTemplate(el, state);
if (inlineTemplate) {
data += inlineTemplate + ",";
}
}
data = data.replace(/,$/, '') + '}';
// v-bind data wrap
if (el.wrapData) {
data = el.wrapData(data);
}
// v-on data wrap
if (el.wrapListeners) {
data = el.wrapListeners(data);
}
return data
}
function genDirectives (el, state) {
var dirs = el.directives;
if (!dirs) { return }
var res = 'directives:[';
var hasRuntime = false;
var i, l, dir, needRuntime;
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i];
needRuntime = true;
var gen = state.directives[dir.name];
if (gen) {
// compile-time directive that manipulates AST.
// returns true if it also needs a runtime counterpart.
needRuntime = !!gen(el, dir, state.warn);
}
if (needRuntime) {
hasRuntime = true;
res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:\"" + (dir.arg) + "\"") : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
}
}
if (hasRuntime) {
return res.slice(0, -1) + ']'
}
}
function genInlineTemplate (el, state) {
var ast = el.children[0];
if ("development" !== 'production' && (
el.children.length > 1 || ast.type !== 1
)) {
state.warn('Inline-template components must have exactly one child element.');
}
if (ast.type === 1) {
var inlineRenderFns = generate(ast, state.options);
return ("inlineTemplate:{render:function(){" + (inlineRenderFns.render) + "},staticRenderFns:[" + (inlineRenderFns.staticRenderFns.map(function (code) { return ("function(){" + code + "}"); }).join(',')) + "]}")
}
}
function genScopedSlots (
slots,
state
) {
return ("scopedSlots:_u([" + (Object.keys(slots).map(function (key) {
return genScopedSlot(key, slots[key], state)
}).join(',')) + "])")
}
function genScopedSlot (
key,
el,
state
) {
if (el.for && !el.forProcessed) {
return genForScopedSlot(key, el, state)
}
return "{key:" + key + ",fn:function(" + (String(el.attrsMap.scope)) + "){" +
"return " + (el.tag === 'template'
? genChildren(el, state) || 'void 0'
: genElement(el, state)) + "}}"
}
function genForScopedSlot (
key,
el,
state
) {
var exp = el.for;
var alias = el.alias;
var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : '';
var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : '';
el.forProcessed = true; // avoid recursion
return "_l((" + exp + ")," +
"function(" + alias + iterator1 + iterator2 + "){" +
"return " + (genScopedSlot(key, el, state)) +
'})'
}
function genChildren (
el,
state,
checkSkip,
altGenElement,
altGenNode
) {
var children = el.children;
if (children.length) {
var el$1 = children[0];
// optimize single v-for
if (children.length === 1 &&
el$1.for &&
el$1.tag !== 'template' &&
el$1.tag !== 'slot'
) {
return (altGenElement || genElement)(el$1, state)
}
var normalizationType = checkSkip
? getNormalizationType(children, state.maybeComponent)
: 0;
var gen = altGenNode || genNode;
return ("[" + (children.map(function (c) { return gen(c, state); }).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
}
}
// determine the normalization needed for the children array.
// 0: no normalization needed
// 1: simple normalization needed (possible 1-level deep nested array)
// 2: full normalization needed
function getNormalizationType (
children,
maybeComponent
) {
var res = 0;
for (var i = 0; i < children.length; i++) {
var el = children[i];
if (el.type !== 1) {
continue
}
if (needsNormalization(el) ||
(el.ifConditions && el.ifConditions.some(function (c) { return needsNormalization(c.block); }))) {
res = 2;
break
}
if (maybeComponent(el) ||
(el.ifConditions && el.ifConditions.some(function (c) { return maybeComponent(c.block); }))) {
res = 1;
}
}
return res
}
function needsNormalization (el) {
return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'
}
function genNode (node, state) {
if (node.type === 1) {
return genElement(node, state)
} if (node.type === 3 && node.isComment) {
return genComment(node)
} else {
return genText(node)
}
}
function genText (text) {
return ("_v(" + (text.type === 2
? text.expression // no need for () because already wrapped in _s()
: transformSpecialNewlines(JSON.stringify(text.text))) + ")")
}
function genComment (comment) {
return ("_e(" + (JSON.stringify(comment.text)) + ")")
}
function genSlot (el, state) {
var slotName = el.slotName || '"default"';
var children = genChildren(el, state);
var res = "_t(" + slotName + (children ? ("," + children) : '');
var attrs = el.attrs && ("{" + (el.attrs.map(function (a) { return ((camelize(a.name)) + ":" + (a.value)); }).join(',')) + "}");
var bind$$1 = el.attrsMap['v-bind'];
if ((attrs || bind$$1) && !children) {
res += ",null";
}
if (attrs) {
res += "," + attrs;
}
if (bind$$1) {
res += (attrs ? '' : ',null') + "," + bind$$1;
}
return res + ')'
}
// componentName is el.component, take it as argument to shun flow's pessimistic refinement
function genComponent (
componentName,
el,
state
) {
var children = el.inlineTemplate ? null : genChildren(el, state, true);
return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")")
}
function genProps (props) {
var res = '';
for (var i = 0; i < props.length; i++) {
var prop = props[i];
res += "\"" + (prop.name) + "\":" + (transformSpecialNewlines(prop.value)) + ",";
}
return res.slice(0, -1)
}
// #3895, #4268
function transformSpecialNewlines (text) {
return text
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029')
}
/* */
// these keywords should not appear inside expressions, but operators like
// typeof, instanceof and in are allowed
var prohibitedKeywordRE = new RegExp('\\b' + (
'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
'super,throw,while,yield,delete,export,import,return,switch,default,' +
'extends,finally,continue,debugger,function,arguments'
).split(',').join('\\b|\\b') + '\\b');
// these unary operators should not be used as property/method names
var unaryOperatorsRE = new RegExp('\\b' + (
'delete,typeof,void'
).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)');
// check valid identifier for v-for
var identRE = /[A-Za-z_$][\w$]*/;
// strip strings in expressions
var stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g;
// detect problematic expressions in a template
function detectErrors (ast) {
var errors = [];
if (ast) {
checkNode(ast, errors);
}
return errors
}
function checkNode (node, errors) {
if (node.type === 1) {
for (var name in node.attrsMap) {
if (dirRE.test(name)) {
var value = node.attrsMap[name];
if (value) {
if (name === 'v-for') {
checkFor(node, ("v-for=\"" + value + "\""), errors);
} else if (onRE.test(name)) {
checkEvent(value, (name + "=\"" + value + "\""), errors);
} else {
checkExpression(value, (name + "=\"" + value + "\""), errors);
}
}
}
}
if (node.children) {
for (var i = 0; i < node.children.length; i++) {
checkNode(node.children[i], errors);
}
}
} else if (node.type === 2) {
checkExpression(node.expression, node.text, errors);
}
}
function checkEvent (exp, text, errors) {
var stipped = exp.replace(stripStringRE, '');
var keywordMatch = stipped.match(unaryOperatorsRE);
if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') {
errors.push(
"avoid using JavaScript unary operator as property name: " +
"\"" + (keywordMatch[0]) + "\" in expression " + (text.trim())
);
}
checkExpression(exp, text, errors);
}
function checkFor (node, text, errors) {
checkExpression(node.for || '', text, errors);
checkIdentifier(node.alias, 'v-for alias', text, errors);
checkIdentifier(node.iterator1, 'v-for iterator', text, errors);
checkIdentifier(node.iterator2, 'v-for iterator', text, errors);
}
function checkIdentifier (ident, type, text, errors) {
if (typeof ident === 'string' && !identRE.test(ident)) {
errors.push(("invalid " + type + " \"" + ident + "\" in expression: " + (text.trim())));
}
}
function checkExpression (exp, text, errors) {
try {
new Function(("return " + exp));
} catch (e) {
var keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE);
if (keywordMatch) {
errors.push(
"avoid using JavaScript keyword as property name: " +
"\"" + (keywordMatch[0]) + "\" in expression " + (text.trim())
);
} else {
errors.push(("invalid expression: " + (text.trim())));
}
}
}
/* */
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err: err, code: code });
return noop
}
}
function createCompileToFunctionFn (compile) {
var cache = Object.create(null);
return function compileToFunctions (
template,
options,
vm
) {
options = options || {};
/* istanbul ignore if */
{
// detect possible CSP restriction
try {
new Function('return 1');
} catch (e) {
if (e.toString().match(/unsafe-eval|CSP/)) {
warn(
'It seems you are using the standalone build of Vue.js in an ' +
'environment with Content Security Policy that prohibits unsafe-eval. ' +
'The template compiler cannot work in this environment. Consider ' +
'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
'templates into render functions.'
);
}
}
}
// check cache
var key = options.delimiters
? String(options.delimiters) + template
: template;
if (cache[key]) {
return cache[key]
}
// compile
var compiled = compile(template, options);
// check compilation errors/tips
{
if (compiled.errors && compiled.errors.length) {
warn(
"Error compiling template:\n\n" + template + "\n\n" +
compiled.errors.map(function (e) { return ("- " + e); }).join('\n') + '\n',
vm
);
}
if (compiled.tips && compiled.tips.length) {
compiled.tips.forEach(function (msg) { return tip(msg, vm); });
}
}
// turn code into functions
var res = {};
var fnGenErrors = [];
res.render = createFunction(compiled.render, fnGenErrors);
res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
return createFunction(code, fnGenErrors)
});
// check function generation errors.
// this should only happen if there is a bug in the compiler itself.
// mostly for codegen development use
/* istanbul ignore if */
{
if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
warn(
"Failed to generate render function:\n\n" +
fnGenErrors.map(function (ref) {
var err = ref.err;
var code = ref.code;
return ((err.toString()) + " in\n\n" + code + "\n");
}).join('\n'),
vm
);
}
}
return (cache[key] = res)
}
}
/* */
function createCompilerCreator (baseCompile) {
return function createCompiler (baseOptions) {
function compile (
template,
options
) {
var finalOptions = Object.create(baseOptions);
var errors = [];
var tips = [];
finalOptions.warn = function (msg, tip) {
(tip ? tips : errors).push(msg);
};
if (options) {
// merge custom modules
if (options.modules) {
finalOptions.modules =
(baseOptions.modules || []).concat(options.modules);
}
// merge custom directives
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives),
options.directives
);
}
// copy other options
for (var key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key];
}
}
}
var compiled = baseCompile(template, finalOptions);
{
errors.push.apply(errors, detectErrors(compiled.ast));
}
compiled.errors = errors;
compiled.tips = tips;
return compiled
}
return {
compile: compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
/* */
// `createCompilerCreator` allows creating compilers that use alternative
// parser/optimizer/codegen, e.g the SSR optimizing compiler.
// Here we just export a default compiler using the default parts.
var createCompiler = createCompilerCreator(function baseCompile (
template,
options
) {
var ast = parse(template.trim(), options);
optimize(ast, options);
var code = generate(ast, options);
return {
ast: ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
});
/* */
var ref$1 = createCompiler(baseOptions);
var compileToFunctions = ref$1.compileToFunctions;
/* */
var idToTemplate = cached(function (id) {
var el = query(id);
return el && el.innerHTML
});
var mount = Vue$3.prototype.$mount;
Vue$3.prototype.$mount = function (
el,
hydrating
) {
el = el && query(el);
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
"development" !== 'production' && warn(
"Do not mount Vue to or - mount to normal elements instead."
);
return this
}
var options = this.$options;
// resolve template/el and convert to render function
if (!options.render) {
var template = options.template;
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template);
/* istanbul ignore if */
if ("development" !== 'production' && !template) {
warn(
("Template element not found or is empty: " + (options.template)),
this
);
}
}
} else if (template.nodeType) {
template = template.innerHTML;
} else {
{
warn('invalid template option:' + template, this);
}
return this
}
} else if (el) {
template = getOuterHTML(el);
}
if (template) {
/* istanbul ignore if */
if ("development" !== 'production' && config.performance && mark) {
mark('compile');
}
var ref = compileToFunctions(template, {
shouldDecodeNewlines: shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments
}, this);
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render;
options.staticRenderFns = staticRenderFns;
/* istanbul ignore if */
if ("development" !== 'production' && config.performance && mark) {
mark('compile end');
measure(((this._name) + " compile"), 'compile', 'compile end');
}
}
}
return mount.call(this, el, hydrating)
};
/**
* Get outerHTML of elements, taking care
* of SVG elements in IE as well.
*/
function getOuterHTML (el) {
if (el.outerHTML) {
return el.outerHTML
} else {
var container = document.createElement('div');
container.appendChild(el.cloneNode(true));
return container.innerHTML
}
}
Vue$3.compile = compileToFunctions;
return Vue$3;
})));
================================================
FILE: webmacs/scheme_handlers/webmacs/templates/base.html
================================================
{% block head %}
{% block title %}{% endblock %} - webmacs
{% endblock %}
{% block content %}{% endblock %}
================================================
FILE: webmacs/scheme_handlers/webmacs/templates/bindings.html
================================================
{% extends "base.html" %}
{% block title %}Bindings{% endblock %}
{% block content %}
Webmacs bindings
{% for name, keymap in keymaps|dictsort %}
{% set km = keymap.all_bindings(with_parent=False) %}
{% if km %}
{{keymap.brief_doc}}
{% if keymap.parent %}
Parent keymap: {{keymap.parent.name}}
{% endif %}
{% for prefix, command in km|sort %}
| {{prefix}} |
{{command}} |
{% endfor %}
{% endif %}
{% endfor %}
{% endblock %}
================================================
FILE: webmacs/scheme_handlers/webmacs/templates/command.html
================================================
{% extends "base.html" %}
{% block title %}{{command_name}} command{% endblock %}
{% block content %}
Command: {{command_name}}
See the source code.
{{command.getdoc()}}
{% if used_in_keymaps %}
This command is bound to:
| binding | keymap |
{% for binding, keymap in used_in_keymaps %}
| {{binding}} | {{keymap}} |
{% endfor %}
{% endif %}
See all commands
or all key bindings .
{% endblock %}
================================================
FILE: webmacs/scheme_handlers/webmacs/templates/commands.html
================================================
{% extends "base.html" %}
{% block title %}Available commands{% endblock %}
{% block content %}
Webmacs commands
{% for name, command in commands|dictsort %}
{% if command.visible %}
| {{name}} |
{{command.getdoc().splitlines()[0]}} |
{% endif %}
{% endfor %}
{% endblock %}
================================================
FILE: webmacs/scheme_handlers/webmacs/templates/downloads.html
================================================
{% extends "base.html" %}
{% block head %}
{% raw %}
{% endraw %}
{% endblock %}
{% block title %}Downloads{% endblock %}
{% block content %}
{% raw %}
|
{{download.state}}
|
|
{{download.path}}
|
{% endraw %}
{% endblock %}
================================================
FILE: webmacs/scheme_handlers/webmacs/templates/key.html
================================================
{% extends "base.html" %}
{% block title %}key {{key}} on {{keymap}}{% endblock %}
{% block content %}
{{key}} run the command
{% if named_command %}
{{command_name}}
{% else %}
{{command_name}}
{% endif %}
(found in {{keymap}} keymap),
which is
{% if named_command %}
an interactive command
{% else %}
a raw python function
{% endif %}
defined in {{modname}}.
It is bound to {{all_keys|join(', ')}}.
{{command_doc}}
{% endblock %}
================================================
FILE: webmacs/scheme_handlers/webmacs/templates/keymap.html
================================================
{% extends "base.html" %}
{% block title %}Keymap {{name}}{% endblock %}
{% block content %}
Keymap: {{name}}
{{keymap.doc}}
{% for k, g in bindings %}
{% if k %}
From parent keymap {{k}}:
{% endif %}
{% for prefix, command, _ in g %}
| {{prefix}} |
{{command}} |
{% endfor %}
{% endfor %}
{% endblock %}
================================================
FILE: webmacs/scheme_handlers/webmacs/templates/variable.html
================================================
{% macro var_type(v) -%}
{% set desc = {"Type": v.type.describe()}.items() %}
{%- for k, v in desc recursive %}
{% set outer_loop = loop %}
- {{k}}: {{v[0]}}({{ v[1]|join(', ') }})
{% for sv in v[2:] %}
{{ outer_loop(sv.items()) }}
{% endfor %}
{% endfor %}
{%- endmacro %}
{% extends "base.html" %}
{% block title %}Variable {{variable.name}}{% endblock %}
{% block content %}
Variable {{variable.name}}
{{var_type(variable)}}
Its current value is
{{variable.value}}.
{{variable.doc}}
See
all variables.
{% endblock %}
================================================
FILE: webmacs/scheme_handlers/webmacs/templates/variables.html
================================================
{% extends "base.html" %}
{% block title %}Available variables{% endblock %}
{% block content %}
Webmacs variables
| name | documentation |
{% for name, variable in variables|dictsort %}
| {{name}} | {{variable.doc}} |
{% endfor %}
{% endblock %}
================================================
FILE: webmacs/scheme_handlers/webmacs/templates/version.html
================================================
{% extends "base.html" %}
{% block title %}Version{% endblock %}
{% block content %}
{% for name, version in versions %}
| {{name}}: | {{version}} |
{% endfor %}
{% endblock %}
================================================
FILE: webmacs/scripts/caret_browsing.js
================================================
// eslint-disable-line max-lines
/**
* Copyright 2016-2017 Florian Bruhin (The Compiler)
*
* This file is part of qutebrowser.
*
* qutebrowser is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* qutebrowser is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with qutebrowser. If not, see .
*/
/**
* Ported chrome-caretbrowsing extension.
* Create and control div caret, which listen commands from qutebrowser,
* change document selection model and div caret position.
*/
// see https://cs.chromium.org/chromium/src/ui/accessibility/extensions/caretbrowsing/
"use strict";
const axs = {};
axs.dom = {};
axs.color = {};
axs.utils = {};
axs.dom.parentElement = function(node) {
if (!node) {
return null;
}
const composedNode = axs.dom.composedParentNode(node);
if (!composedNode) {
return null;
}
switch (composedNode.nodeType) {
case Node.ELEMENT_NODE:
return composedNode;
default:
return axs.dom.parentElement(composedNode);
}
};
axs.dom.shadowHost = function(node) {
if ("host" in node) {
return node.host;
}
return null;
};
axs.dom.composedParentNode = function(node) {
if (!node) {
return null;
}
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
return axs.dom.shadowHost(node);
}
const parentNode = node.parentNode;
if (!parentNode) {
return null;
}
if (parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
return axs.dom.shadowHost(parentNode);
}
if (!parentNode.shadowRoot) {
return parentNode;
}
const points = node.getDestinationInsertionPoints();
if (points.length > 0) {
return axs.dom.composedParentNode(points[points.length - 1]);
}
return null;
};
axs.color.Color = function(red, green, blue, alpha) { // eslint-disable-line max-params,max-len
this.red = red;
this.green = green;
this.blue = blue;
this.alpha = alpha;
};
axs.color.parseColor = function(colorText) {
if (colorText === "transparent") {
return new axs.color.Color(0, 0, 0, 0);
}
let match = colorText.match(/^rgb\((\d+), (\d+), (\d+)\)$/);
if (match) {
const blue = parseInt(match[3], 10);
const green = parseInt(match[2], 10);
const red = parseInt(match[1], 10);
return new axs.color.Color(red, green, blue, 1);
}
match = colorText.match(/^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/);
if (match) {
const red = parseInt(match[1], 10);
const green = parseInt(match[2], 10);
const blue = parseInt(match[3], 10);
const alpha = parseFloat(match[4]);
return new axs.color.Color(red, green, blue, alpha);
}
return null;
};
axs.color.flattenColors = function(color1, color2) {
const colorAlpha = color1.alpha;
return new axs.color.Color(
((1 - colorAlpha) * color2.red) + (colorAlpha * color1.red),
((1 - colorAlpha) * color2.green) + (colorAlpha * color1.green),
((1 - colorAlpha) * color2.blue) + (colorAlpha * color2.blue),
color1.alpha + (color2.alpha * (1 - color1.alpha)));
};
axs.utils.getParentBgColor = function(_el) {
let el = _el;
let el2 = el;
let iter = null;
el = [];
for (iter = null; (el2 = axs.dom.parentElement(el2));) {
const style = window.getComputedStyle(el2, null);
if (style) {
const color = axs.color.parseColor(style.backgroundColor);
if (color &&
(style.opacity < 1 &&
(color.alpha *= style.opacity),
color.alpha !== 0 &&
(el.push(color), color.alpha === 1))) {
iter = !0;
break;
}
}
}
if (!iter) {
el.push(new axs.color.Color(255, 255, 255, 1));
}
for (el2 = el.pop(); el.length;) {
iter = el.pop();
el2 = axs.color.flattenColors(iter, el2);
}
return el2;
};
axs.utils.getFgColor = function(el, el2, color) {
let color2 = axs.color.parseColor(el.color);
if (!color2) {
return null;
}
if (color2.alpha < 1) {
color2 = axs.color.flattenColors(color2, color);
}
if (el.opacity < 1) {
const el3 = axs.utils.getParentBgColor(el2);
color2.alpha *= el.opacity;
color2 = axs.color.flattenColors(color2, el3);
}
return color2;
};
axs.utils.getBgColor = function(el, elParent) {
let color = axs.color.parseColor(el.backgroundColor);
if (!color) {
return null;
}
if (el.opacity < 1) {
color.alpha *= el.opacity;
}
if (color.alpha < 1) {
const bgColor = axs.utils.getParentBgColor(elParent);
if (bgColor === null) {
return null;
}
color = axs.color.flattenColors(color, bgColor);
}
return color;
};
axs.color.colorChannelToString = function(_color) {
const color = Math.round(_color);
if (color < 15) {
return `0${color.toString(16)}`;
}
return color.toString(16);
};
axs.color.colorToString = function(color) {
if (color.alpha === 1) {
const red = axs.color.colorChannelToString(color.red);
const green = axs.color.colorChannelToString(color.green);
const blue = axs.color.colorChannelToString(color.blue);
return `#${red}${green}${blue}`;
}
const arr = [color.red, color.green, color.blue, color.alpha].join();
return `rgba(${arr})`;
};
const Cursor = function(node, index, text) { // eslint-disable-line func-style,max-len
this.node = node;
this.index = index;
this.text = text;
};
Cursor.prototype.clone = function() {
return new Cursor(this.node, this.index, this.text);
};
Cursor.prototype.copyFrom = function(otherCursor) {
this.node = otherCursor.node;
this.index = otherCursor.index;
this.text = otherCursor.text;
};
const TraverseUtil = {};
TraverseUtil.getNodeText = function(node) {
if (node.constructor === Text) {
return node.data;
}
return "";
};
TraverseUtil.treatAsLeafNode = function(node) {
return node.childNodes.length === 0 ||
node.nodeName === "SELECT" ||
node.nodeName === "OBJECT";
};
TraverseUtil.isWhitespace = function(ch) {
return (ch === " " || ch === "\n" || ch === "\r" || ch === "\t");
};
TraverseUtil.isVisible = function(node) {
if (!node.style) {
return true;
}
const style = window.getComputedStyle(node, null);
return (Boolean(style) &&
style.display !== "none" &&
style.visibility !== "hidden");
};
TraverseUtil.isSkipped = function(_node) {
let node = _node;
if (node.constructor === Text) {
node = node.parentElement;
}
if (node.className === "CaretBrowsing_Caret" ||
node.className === "CaretBrowsing_AnimateCaret") {
return true;
}
return false;
};
TraverseUtil.forwardsChar = function(cursor, nodesCrossed) { // eslint-disable-line max-statements,max-len
for (;;) {
let childNode = null;
if (!TraverseUtil.treatAsLeafNode(cursor.node)) {
for (let i = cursor.index;
i < cursor.node.childNodes.length;
i++) {
const node = cursor.node.childNodes[i];
if (TraverseUtil.isSkipped(node)) {
nodesCrossed.push(node);
} else if (TraverseUtil.isVisible(node)) {
childNode = node;
break;
}
}
}
if (childNode) {
cursor.node = childNode;
cursor.index = 0;
cursor.text = TraverseUtil.getNodeText(cursor.node);
if (cursor.node.constructor !== Text) {
nodesCrossed.push(cursor.node);
}
} else {
if (cursor.index < cursor.text.length) {
return cursor.text[cursor.index++];
}
while (cursor.node !== null) {
let siblingNode = null;
for (let node = cursor.node.nextSibling;
node !== null;
node = node.nextSibling) {
if (TraverseUtil.isSkipped(node)) {
nodesCrossed.push(node);
} else if (TraverseUtil.isVisible(node)) {
siblingNode = node;
break;
}
}
if (siblingNode) {
cursor.node = siblingNode;
cursor.text = TraverseUtil.getNodeText(siblingNode);
cursor.index = 0;
if (cursor.node.constructor !== Text) {
nodesCrossed.push(cursor.node);
}
break;
}
const parentNode = cursor.node.parentNode;
if (parentNode &&
parentNode.constructor !== HTMLBodyElement) {
cursor.node = cursor.node.parentNode;
cursor.text = null;
cursor.index = 0;
} else {
return null;
}
}
}
}
};
TraverseUtil.getNextChar = function( // eslint-disable-line max-params
startCursor, endCursor, nodesCrossed, skipWhitespace) {
startCursor.copyFrom(endCursor);
let fChar = TraverseUtil.forwardsChar(endCursor, nodesCrossed);
if (fChar === null) {
return null;
}
const initialWhitespace = TraverseUtil.isWhitespace(fChar);
while ((TraverseUtil.isWhitespace(fChar)) ||
(TraverseUtil.isSkipped(endCursor.node))) {
fChar = TraverseUtil.forwardsChar(endCursor, nodesCrossed);
if (fChar === null) {
return null;
}
}
if (skipWhitespace || !initialWhitespace) {
startCursor.copyFrom(endCursor);
startCursor.index--;
return fChar;
}
for (let i = 0; i < nodesCrossed.length; i++) {
if (TraverseUtil.isSkipped(nodesCrossed[i])) {
endCursor.index--;
startCursor.copyFrom(endCursor);
startCursor.index--;
return " ";
}
}
endCursor.index--;
return " ";
};
TraverseUtil.backwardsChar = function(cursor, nodesCrossed) {
while (true) {
// Move down until we get to a leaf node.
var childNode = null;
if (!TraverseUtil.treatAsLeafNode(cursor.node)) {
for (var i = cursor.index - 1; i >= 0; i--) {
var node = cursor.node.childNodes[i];
if (TraverseUtil.isSkipped(node)) {
nodesCrossed.push(node);
continue;
}
if (TraverseUtil.isVisible(node)) {
childNode = node;
break;
}
}
}
if (childNode) {
cursor.node = childNode;
cursor.text = TraverseUtil.getNodeText(cursor.node);
if (cursor.text.length)
cursor.index = cursor.text.length;
else
cursor.index = cursor.node.childNodes.length;
if (cursor.node.constructor != Text)
nodesCrossed.push(cursor.node);
continue;
}
// Return the previous character from this leaf node.
if (cursor.text.length > 0 && cursor.index > 0) {
return cursor.text[--cursor.index];
}
// Move to the previous sibling, going up the tree as necessary.
while (true) {
// Try to move to the previous sibling.
var siblingNode = null;
for (var node = cursor.node.previousSibling;
node != null;
node = node.previousSibling) {
if (TraverseUtil.isSkipped(node)) {
nodesCrossed.push(node);
continue;
}
if (TraverseUtil.isVisible(node)) {
siblingNode = node;
break;
}
}
if (siblingNode) {
cursor.node = siblingNode;
cursor.text = TraverseUtil.getNodeText(siblingNode);
if (cursor.text.length)
cursor.index = cursor.text.length;
else
cursor.index = cursor.node.childNodes.length;
if (cursor.node.constructor != Text)
nodesCrossed.push(cursor.node);
break;
}
// Otherwise, move to the parent.
if (cursor.node.parentNode &&
cursor.node.parentNode.constructor != HTMLBodyElement) {
cursor.node = cursor.node.parentNode;
cursor.text = null;
cursor.index = 0;
} else {
return null;
}
}
}
};
TraverseUtil.getNextWord = function(startCursor, endCursor,
nodesCrossed) {
// Find the first non-whitespace or non-skipped character.
var cursor = endCursor.clone();
var c = TraverseUtil.forwardsChar(cursor, nodesCrossed);
if (c == null)
return null;
while ((TraverseUtil.isWhitespace(c)) ||
(TraverseUtil.isSkipped(cursor.node))) {
c = TraverseUtil.forwardsChar(cursor, nodesCrossed);
if (c == null)
return null;
}
// Set startCursor to the position immediately before the first
// character in our word. It's safe to decrement |index| because
// forwardsChar guarantees that the cursor will be immediately to the
// right of the returned character on exit.
startCursor.copyFrom(cursor);
startCursor.index--;
// Keep building up our word until we reach a whitespace character or
// would cross a tag. Don't actually return any tags crossed, because this
// word goes up until the tag boundary but not past it.
endCursor.copyFrom(cursor);
var word = c;
var newNodesCrossed = [];
c = TraverseUtil.forwardsChar(cursor, newNodesCrossed);
if (c == null) {
return word;
}
while (!TraverseUtil.isWhitespace(c) &&
newNodesCrossed.length == 0) {
word += c;
endCursor.copyFrom(cursor);
c = TraverseUtil.forwardsChar(cursor, newNodesCrossed);
if (c == null) {
return word;
}
}
return word;
};
TraverseUtil.getPreviousWord = function(startCursor, endCursor,
nodesCrossed) {
// Find the first non-whitespace or non-skipped character.
var cursor = startCursor.clone();
var c = TraverseUtil.backwardsChar(cursor, nodesCrossed);
if (c == null)
return null;
while ((TraverseUtil.isWhitespace(c) ||
(TraverseUtil.isSkipped(cursor.node)))) {
c = TraverseUtil.backwardsChar(cursor, nodesCrossed);
if (c == null)
return null;
}
// Set endCursor to the position immediately after the first
// character we've found (the last character of the word, since we're
// searching backwards).
endCursor.copyFrom(cursor);
endCursor.index++;
// Keep building up our word until we reach a whitespace character or
// would cross a tag. Don't actually return any tags crossed, because this
// word goes up until the tag boundary but not past it.
startCursor.copyFrom(cursor);
var word = c;
var newNodesCrossed = [];
c = TraverseUtil.backwardsChar(cursor, newNodesCrossed);
if (c == null)
return word;
while (!TraverseUtil.isWhitespace(c) &&
newNodesCrossed.length == 0) {
word = c + word;
startCursor.copyFrom(cursor);
c = TraverseUtil.backwardsChar(cursor, newNodesCrossed);
if (c == null)
return word;
}
return word;
};
const CaretBrowsing = {};
CaretBrowsing.isEnabled = false;
CaretBrowsing.onEnable = "flash";
CaretBrowsing.onJump = "flash";
CaretBrowsing.isWindowFocused = false;
CaretBrowsing.isCaretVisible = false;
CaretBrowsing.caretElement = undefined;
CaretBrowsing.caretX = 0;
CaretBrowsing.caretY = 0;
CaretBrowsing.caretWidth = 0;
CaretBrowsing.caretHeight = 0;
CaretBrowsing.caretForeground = "#000";
CaretBrowsing.caretBackground = "#fff";
CaretBrowsing.isSelectionCollapsed = false;
CaretBrowsing.blinkFunctionId = null;
CaretBrowsing.targetX = null;
CaretBrowsing.blinkFlag = true;
CaretBrowsing.markEnabled = false;
CaretBrowsing.positionCaret = function() {
var start = new Cursor(document.body, 0, '');
var end = new Cursor(document.body, 0, '');
var nodesCrossed = [];
var result = TraverseUtil.getNextChar(start, end, nodesCrossed, true);
if (result == null) {
return;
}
CaretBrowsing.setAndValidateSelection(start, start);
}
CaretBrowsing.isFocusable = function(targetNode) {
if (!targetNode || typeof (targetNode.tabIndex) !== "number") {
return false;
}
if (targetNode.tabIndex >= 0) {
return true;
}
if (targetNode.hasAttribute &&
targetNode.hasAttribute("tabindex") &&
targetNode.getAttribute("tabindex") === "-1") {
return true;
}
return false;
}
CaretBrowsing.isControlThatNeedsArrowKeys = function(node) { // eslint-disable-line complexity,max-len
if (!node) {
return false;
}
if (node === document.body || node !== document.activeElement) {
return false;
}
if (node.constructor === HTMLSelectElement) {
return true;
}
if (node.constructor === HTMLInputElement) {
switch (node.type) { // eslint-disable-line default-case
case "email":
case "number":
case "password":
case "search":
case "text":
case "tel":
case "url":
case "":
return true;
case "datetime":
case "datetime-local":
case "date":
case "month":
case "radio":
case "range":
case "week":
return true;
}
}
if (node.getAttribute && CaretBrowsing.isFocusable(node)) {
const role = node.getAttribute("role");
switch (role) { // eslint-disable-line default-case
case "combobox":
case "grid":
case "gridcell":
case "listbox":
case "menu":
case "menubar":
case "menuitem":
case "menuitemcheckbox":
case "menuitemradio":
case "option":
case "radiogroup":
case "scrollbar":
case "slider":
case "spinbutton":
case "tab":
case "tablist":
case "textbox":
case "tree":
case "treegrid":
case "treeitem":
return true;
}
}
return false;
};
CaretBrowsing.injectCaretStyles = function() {
const style = ".CaretBrowsing_Caret {" +
" position: absolute;" +
" z-index: 2147483647;" +
" min-height: 10px;" +
" background-color: #000;" +
"}" +
".CaretBrowsing_AnimateCaret {" +
" position: absolute;" +
" z-index: 2147483647;" +
" min-height: 10px;" +
"}" +
".CaretBrowsing_FlashVert {" +
" position: absolute;" +
" z-index: 2147483647;" +
" background: linear-gradient(" +
" 270deg," +
" rgba(128, 128, 255, 0) 0%," +
" rgba(128, 128, 255, 0.3) 45%," +
" rgba(128, 128, 255, 0.8) 50%," +
" rgba(128, 128, 255, 0.3) 65%," +
" rgba(128, 128, 255, 0) 100%);" +
"}";
const node = document.createElement("style");
node.innerHTML = style;
document.body.appendChild(node);
};
CaretBrowsing.setInitialCursor = function() {
if (CaretBrowsing.post_message_down("CaretBrowsing.setInitialCursor")) {
return;
}
const sel = window.getSelection();
if (!CaretBrowsing.initiated) {
if (sel.rangeCount == 0) {
CaretBrowsing.positionCaret();
}
CaretBrowsing.injectCaretStyles();
CaretBrowsing.initiated = true;
}
CaretBrowsing.toggle();
if (CaretBrowsing.isEnabled) {
// when doing an i-search, the selection is totally cleared (sigh)
if (sel.rangeCount == 0) {
CaretBrowsing.positionCaret();
}
// do not handle previous selection for now, just removes it.
CaretBrowsing.markEnabled = sel.type == "Range";
if (CaretBrowsing.markEnabled) {
CaretBrowsing.markEnabled = false;
sel.collapse(sel.anchorNode, sel.anchorOffset);
window.setTimeout(() => {
CaretBrowsing.updateCaretOrSelection(true);
}, 0);
}
}
};
CaretBrowsing.shutdown = function() {
if (CaretBrowsing.post_message_down("CaretBrowsing.shutdown")) {
return;
}
CaretBrowsing.toggle(false);
}
CaretBrowsing.setAndValidateSelection = function(start, end) {
const sel = window.getSelection();
sel.setBaseAndExtent(start.node, start.index, end.node, end.index);
if (sel.rangeCount !== 1) {
return false;
}
return (sel.anchorNode === start.node &&
sel.anchorOffset === start.index &&
sel.focusNode === end.node &&
sel.focusOffset === end.index);
};
CaretBrowsing.setCaretElementNormalStyle = function() {
const element = CaretBrowsing.caretElement;
element.className = "CaretBrowsing_Caret";
if (CaretBrowsing.isSelectionCollapsed) {
element.style.opacity = "1.0";
} else {
element.style.opacity = "0.0";
}
element.style.left = `${CaretBrowsing.caretX}px`;
element.style.top = `${CaretBrowsing.caretY}px`;
element.style.width = `${CaretBrowsing.caretWidth}px`;
element.style.height = `${CaretBrowsing.caretHeight}px`;
element.style.color = CaretBrowsing.caretForeground;
};
CaretBrowsing.animateCaretElement = function() {
const element = CaretBrowsing.caretElement;
element.style.left = `${CaretBrowsing.caretX - 50}px`;
element.style.top = `${CaretBrowsing.caretY - 100}px`;
element.style.width = `${CaretBrowsing.caretWidth + 100}px`;
element.style.height = `${CaretBrowsing.caretHeight + 200}px`;
element.className = "CaretBrowsing_AnimateCaret";
window.setTimeout(() => {
if (!CaretBrowsing.caretElement) {
return;
}
CaretBrowsing.setCaretElementNormalStyle();
element.style.transition = "all 0.8s ease-in";
function listener() {
element.removeEventListener(
"transitionend", listener, false);
element.style.transition = "none";
}
element.addEventListener(
"transitionend", listener, false);
}, 0);
};
CaretBrowsing.flashCaretElement = function() {
const x = CaretBrowsing.caretX;
const y = CaretBrowsing.caretY;
const vert = document.createElement("div");
vert.className = "CaretBrowsing_FlashVert";
vert.style.left = `${x - 6}px`;
vert.style.top = `${y - 100}px`;
vert.style.width = "11px";
vert.style.height = `${200}px`;
document.body.appendChild(vert);
window.setTimeout(() => {
document.body.removeChild(vert);
if (CaretBrowsing.caretElement) {
CaretBrowsing.setCaretElementNormalStyle();
}
}, 250);
};
CaretBrowsing.createCaretElement = function() {
const element = document.createElement("div");
element.className = "CaretBrowsing_Caret";
document.body.appendChild(element);
CaretBrowsing.caretElement = element;
if (CaretBrowsing.onEnable === "anim") {
CaretBrowsing.animateCaretElement();
} else if (CaretBrowsing.onEnable === "flash") {
CaretBrowsing.flashCaretElement();
} else {
CaretBrowsing.setCaretElementNormalStyle();
}
};
CaretBrowsing.getCursorRect = function(cursor) { // eslint-disable-line max-statements,max-len
let node = cursor.node;
const index = cursor.index;
const rect = {
"left": 0,
"top": 0,
"width": 1,
"height": 0,
};
if (node.constructor === Text) {
let left = index;
let right = index;
const max = node.data.length;
const newRange = document.createRange();
while (left > 0 || right < max) {
if (left > 0) {
left--;
newRange.setStart(node, left);
newRange.setEnd(node, index);
const rangeRect = newRange.getBoundingClientRect();
if (rangeRect && rangeRect.width && rangeRect.height) {
rect.left = rangeRect.right;
rect.top = rangeRect.top;
rect.height = rangeRect.height;
break;
}
}
if (right < max) {
right++;
newRange.setStart(node, index);
newRange.setEnd(node, right);
const rangeRect = newRange.getBoundingClientRect();
if (rangeRect && rangeRect.width && rangeRect.height) {
rect.left = rangeRect.left;
rect.top = rangeRect.top;
rect.height = rangeRect.height;
break;
}
}
}
} else {
rect.height = node.offsetHeight;
while (node !== null) {
rect.left += node.offsetLeft;
rect.top += node.offsetTop;
node = node.offsetParent;
}
}
rect.left += window.pageXOffset;
rect.top += window.pageYOffset;
return rect;
};
CaretBrowsing.updateCaretOrSelection =
function(scrollToSelection) { // eslint-disable-line max-statements
const previousX = CaretBrowsing.caretX;
const previousY = CaretBrowsing.caretY;
const sel = window.getSelection();
if (sel.rangeCount === 0) {
if (CaretBrowsing.caretElement) {
CaretBrowsing.isSelectionCollapsed = false;
CaretBrowsing.caretElement.style.opacity = "0.0";
}
return;
}
const range = sel.getRangeAt(0);
if (!range) {
if (CaretBrowsing.caretElement) {
CaretBrowsing.isSelectionCollapsed = false;
CaretBrowsing.caretElement.style.opacity = "0.0";
}
return;
}
if (CaretBrowsing.isControlThatNeedsArrowKeys(
document.activeElement)) {
let node = document.activeElement;
CaretBrowsing.caretWidth = node.offsetWidth;
CaretBrowsing.caretHeight = node.offsetHeight;
CaretBrowsing.caretX = 0;
CaretBrowsing.caretY = 0;
while (node.offsetParent) {
CaretBrowsing.caretX += node.offsetLeft;
CaretBrowsing.caretY += node.offsetTop;
node = node.offsetParent;
}
CaretBrowsing.isSelectionCollapsed = false;
} else if (range.startOffset !== range.endOffset ||
range.startContainer !== range.endContainer) {
const rect = range.getBoundingClientRect();
if (!rect) {
return;
}
CaretBrowsing.caretX = rect.left + window.pageXOffset;
CaretBrowsing.caretY = rect.top + window.pageYOffset;
CaretBrowsing.caretWidth = rect.width;
CaretBrowsing.caretHeight = rect.height;
CaretBrowsing.isSelectionCollapsed = false;
} else {
const rect = CaretBrowsing.getCursorRect(
new Cursor(range.startContainer,
range.startOffset,
TraverseUtil.getNodeText(range.startContainer)));
CaretBrowsing.caretX = rect.left;
CaretBrowsing.caretY = rect.top;
CaretBrowsing.caretWidth = rect.width;
CaretBrowsing.caretHeight = rect.height;
CaretBrowsing.isSelectionCollapsed = true;
}
if (CaretBrowsing.caretElement) {
const element = CaretBrowsing.caretElement;
if (CaretBrowsing.isSelectionCollapsed) {
element.style.opacity = "1.0";
element.style.left = `${CaretBrowsing.caretX}px`;
element.style.top = `${CaretBrowsing.caretY}px`;
element.style.width = `${CaretBrowsing.caretWidth}px`;
element.style.height = `${CaretBrowsing.caretHeight}px`;
} else {
element.style.opacity = "0.0";
}
} else {
CaretBrowsing.createCaretElement();
}
let elem = range.startContainer;
if (elem.constructor === Text) {
elem = elem.parentElement;
}
const style = window.getComputedStyle(elem);
const bg = axs.utils.getBgColor(style, elem);
const fg = axs.utils.getFgColor(style, elem, bg);
CaretBrowsing.caretBackground = axs.color.colorToString(bg);
CaretBrowsing.caretForeground = axs.color.colorToString(fg);
if (scrollToSelection) {
const rect = CaretBrowsing.getCursorRect(
new Cursor(sel.focusNode, sel.focusOffset,
TraverseUtil.getNodeText(sel.focusNode)));
const yscroll = window.pageYOffset;
const pageHeight = window.innerHeight;
const caretY = rect.top;
const caretHeight = Math.min(rect.height, 30);
if (yscroll + pageHeight < caretY + caretHeight) {
window.scroll(0, (caretY + caretHeight - pageHeight + 100));
} else if (caretY < yscroll) {
window.scroll(0, (caretY - 100));
}
}
if (Math.abs(previousX - CaretBrowsing.caretX) > 500 ||
Math.abs(previousY - CaretBrowsing.caretY) > 100) {
if (CaretBrowsing.onJump === "anim") {
CaretBrowsing.animateCaretElement();
} else if (CaretBrowsing.onJump === "flash") {
CaretBrowsing.flashCaretElement();
}
}
};
CaretBrowsing.toggle = function(enabled) {
if (enabled == undefined) {
enabled = !CaretBrowsing.isEnabled;
}
CaretBrowsing.isEnabled = enabled;
const obj = {};
obj.enabled = CaretBrowsing.isEnabled;
CaretBrowsing.updateIsCaretVisible();
post_webmacs_message("onCaretBrowsing", [obj.enabled]);
if (!obj.enabled) {
var sel = window.getSelection();
sel.collapse(sel.focusNode, sel.focusOffset);
}
};
CaretBrowsing.onClick = function() {
if (!CaretBrowsing.isEnabled) {
return true;
}
window.setTimeout(() => {
CaretBrowsing.targetX = null;
CaretBrowsing.updateCaretOrSelection(false);
}, 0);
return true;
};
CaretBrowsing.caretBlinkFunction = function() {
if (CaretBrowsing.caretElement) {
if (CaretBrowsing.blinkFlag) {
CaretBrowsing.caretElement.style.backgroundColor =
CaretBrowsing.caretForeground;
CaretBrowsing.blinkFlag = false;
} else {
CaretBrowsing.caretElement.style.backgroundColor =
CaretBrowsing.caretBackground;
CaretBrowsing.blinkFlag = true;
}
}
};
CaretBrowsing.updateIsCaretVisible = function() {
CaretBrowsing.isCaretVisible =
(CaretBrowsing.isEnabled && CaretBrowsing.isWindowFocused);
if (CaretBrowsing.isCaretVisible && !CaretBrowsing.caretElement) {
// CaretBrowsing.setInitialCursor();
CaretBrowsing.updateCaretOrSelection(true);
if (CaretBrowsing.caretElement) {
CaretBrowsing.blinkFunctionId = window.setInterval(
CaretBrowsing.caretBlinkFunction, 500);
}
} else if (!CaretBrowsing.isCaretVisible &&
CaretBrowsing.caretElement) {
window.clearInterval(CaretBrowsing.blinkFunctionId);
if (CaretBrowsing.caretElement) {
CaretBrowsing.isSelectionCollapsed = false;
CaretBrowsing.caretElement.parentElement.removeChild(
CaretBrowsing.caretElement);
CaretBrowsing.caretElement = null;
}
}
};
CaretBrowsing.onWindowFocus = function() {
CaretBrowsing.isWindowFocused = true;
CaretBrowsing.updateIsCaretVisible();
};
CaretBrowsing.onWindowBlur = function() {
CaretBrowsing.isWindowFocused = false;
CaretBrowsing.updateIsCaretVisible();
};
CaretBrowsing.init = function() {
CaretBrowsing.isWindowFocused = document.hasFocus();
document.addEventListener("click", CaretBrowsing.onClick, false);
window.addEventListener("focus", CaretBrowsing.onWindowFocus, false);
window.addEventListener("blur", CaretBrowsing.onWindowBlur, false);
};
window.setTimeout(() => {
if (!window.caretBrowsingLoaded) {
window.caretBrowsingLoaded = true;
CaretBrowsing.init();
}
}, 0);
CaretBrowsing.move = function(direction, granularity) {
if (CaretBrowsing.post_message_down("CaretBrowsing.move",
[direction, granularity])) {
return;
}
var sel = window.getSelection();
var action = CaretBrowsing.markEnabled ? "extend" : "move";
sel.modify(action, direction, granularity);
window.setTimeout(() => {
CaretBrowsing.updateCaretOrSelection(true);
}, 0);
};
CaretBrowsing.toggleMark = function() {
if (CaretBrowsing.post_message_down("CaretBrowsing.toggleMark")) {
return;
}
CaretBrowsing.markEnabled = !CaretBrowsing.markEnabled;
if (!CaretBrowsing.markEnabled) {
var sel = window.getSelection();
sel.collapse(sel.focusNode, sel.focusOffset);
window.setTimeout(() => {
CaretBrowsing.updateCaretOrSelection(true);
}, 0);
}
};
CaretBrowsing.cutSelection = function() {
if (CaretBrowsing.post_message_down("CaretBrowsing.cutSelection")) {
return;
}
post_webmacs_message("copyToClipboard",
[window.getSelection().toString()]);
// clear the current selection
if (CaretBrowsing.markEnabled) { CaretBrowsing.toggleMark(); }
};
CaretBrowsing.post_message_down = function(message_name, arg) {
if (document.activeElement.tagName === "IFRAME") {
post_message(document.activeElement.contentWindow, message_name, arg);
return true;
}
return false;
}
if (self !== top) {
register_message_handler("CaretBrowsing.setInitialCursor",
CaretBrowsing.setInitialCursor);
register_message_handler("CaretBrowsing.shutdown",
CaretBrowsing.shutdown);
register_message_handler("CaretBrowsing.move", function (args) {
CaretBrowsing.move(args[0], args[1]);
});
register_message_handler("CaretBrowsing.toggleMark",
CaretBrowsing.toggleMark);
register_message_handler("CaretBrowsing.cutSelection",
CaretBrowsing.cutSelection);
}
================================================
FILE: webmacs/scripts/hint.js
================================================
function clickLike(elem) {
elem.focus();
var doc = elem.ownerDocument;
var view = doc.defaultView;
var evt = doc.createEvent("MouseEvents");
evt.initMouseEvent("mousedown", true, true, view, 1, 0, 0, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
/*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
elem.dispatchEvent(evt);
evt = doc.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, view, 1, 0, 0, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
/*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
elem.dispatchEvent(evt);
evt = doc.createEvent("MouseEvents");
evt.initMouseEvent("mouseup", true, true, view, 1, 0, 0, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
/*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
elem.dispatchEvent(evt);
}
function rectElementInViewport(elem, w) { // eslint-disable-line complexity
var win = elem.ownerDocument.defaultView;
var rect = elem.getBoundingClientRect();
w = w || window;
if (!rect ||
rect.top > w.innerHeight ||
rect.bottom < 0 ||
rect.left > w.innerWidth ||
rect.right < 0) {
return null;
}
rect = elem.getClientRects()[0];
if (!rect) {
return null;
}
var style = win.getComputedStyle(elem, null);
if (style.getPropertyValue("visibility") !== "visible" ||
style.getPropertyValue("display") === "none" ||
style.getPropertyValue("opacity") === "0") {
return null;
}
return rect;
}
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
class BaseHint {
constructor(obj, manager, index, rect) {
this.obj = obj;
this.manager = manager;
this.index = index;
this.hint = document.createElement("div");
for (var prop in manager.options.hint) {
this.hint.style[prop] = manager.options.hint[prop];
}
this.hint.style.left = (rect.left + window.scrollX) + "px";
this.hint.style.top = (rect.top + window.scrollY) + "px";
this.hint.style.position = "absolute";
this.hint.style.display = "block";
this.hint.style.zIndex = "2147483647";
this.hint.textContent = index;
}
text() {
if (this.obj.textContent) {
return this.obj.textContent;
}
return null;
}
url() {
if (this.obj.href) {
return this.obj.href;
}
return null;
}
id() {
return this.index;
}
serialize() {
return JSON.stringify({
nodeName: this.obj.nodeName,
text: this.text(),
id: this.id(),
url: this.url()
});
}
remove() {
this.hint.parentNode.removeChild(this.hint);
}
setVisible(on) {
this.hint.style.display = on ? "initial" : "none";
}
isVisible() {
return this.hint.style.display != "none";
}
refresh() {}
}
class Hint extends BaseHint {
constructor(obj, manager, rect, index) {
super(obj, manager, index, rect);
this.objBackground = obj.style.background;
this.objColor = obj.style.color;
obj.style.background = manager.options.background;
obj.style.color = manager.options.text_color;
}
remove() {
this.obj.style.background = this.objBackground;
this.obj.style.color = this.objColor;
super.remove();
}
setVisible(on) {
super.setVisible(on);
this.refresh();
}
refresh() {
if (this.isVisible()) {
if (this.manager.activeHint == this) {
this.obj.style.background = this.manager.options.background_active;
} else {
this.obj.style.background = this.manager.options.background;
}
this.obj.style.color = this.manager.options.text_color;
} else {
this.obj.style.background = this.objBackground;
this.obj.style.color = this.objColor;
}
}
id() {
return this.hint.textContent;
}
}
class AlphabetHint extends BaseHint {
constructor(obj, manager, rect, index) {
super(obj, manager, index, rect);
}
}
class HintFrame {
constructor(frame) {
this.frame = frame;
}
remove() {
post_message(this.frame.contentWindow, "hints.select_clear", null);
}
}
// took from conkeror
XPATH_NS = {
xhtml: "http://www.w3.org/1999/xhtml",
m: "http://www.w3.org/1998/Math/MathML",
xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
svg: "http://www.w3.org/2000/svg"
};
function xpath_lookup_namespace (prefix) {
return XPATH_NS[prefix] || null;
}
class Hinter {
init(selector, method, options) {
this.selector = selector;
this.xres = document.evaluate(selector, document, xpath_lookup_namespace,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null);
this.fragment = document.createDocumentFragment();
this.index = 0;
this.hints = [];
this.activeHint = null;
this.method = method;
this.options = options || {};
}
lookup(hint_index) {
// has been cleared.
if (this.hints === null) {
return;
}
for (; this.index < this.xres.snapshotLength; this.index++) {
let obj = this.xres.snapshotItem(this.index);
let rect = rectElementInViewport(obj, window);
if (!rect) {
continue;
}
if (obj.tagName == "IFRAME") {
post_message(obj.contentWindow, "hints.lookup_in_iframe_start",
{selector: this.selector, hint_index: hint_index,
method: this.method, options: this.options});
this.hints.push(new HintFrame(obj));
this.index+=1;
return;
}
hint_index += 1;
var hint;
if (this.method == "filter") {
hint = new Hint(obj, this, rect, hint_index);
} else {
hint = new AlphabetHint(obj, this, rect, hint_index);
hint.setVisible(false);
}
this.hints.push(hint);
this.fragment.appendChild(hint.hint);
}
document.documentElement.appendChild(this.fragment);
if (self !== top) {
post_message(parent, "hints.lookup_in_iframe_end", hint_index);
} else {
if (this.method == "alphabet") {
this.hintsCreated(hint_index);
} else {
// hints are ready
post_webmacs_message("_browserObjectActivated", ["null"]);
}
}
}
// alphabet only
hintsCreated(count) {
// took from vimium
let hints = [""];
let offset = 0;
while ((hints.length - offset) < count || hints.length == 1) {
let hint = hints[offset++];
for (var ch of this.options.characters) {
hints.push(ch + hint);
}
}
hints = hints.slice(offset, offset+count).sort().map(e => {
return e.split("").reverse().join("");
});
this.configure_hints(0, hints, []);
}
// alphabet only
configure_hints(index, labels, parent_indexes) {
let label_index = 0;
for (; index < this.hints.length; index++) {
let hint = this.hints[index];
if (hint instanceof HintFrame) {
post_message(hint.frame.contentWindow, "hints.frame_configure_hints",
{index: 0, labels: labels.slice(label_index),
parent_indexes: [index + 1].concat(parent_indexes)});
return;
} else {
hint.chars = labels[label_index];
hint.hint.textContent = hint.chars;
hint.setVisible(true);
label_index++;
}
}
if (self !== top) {
post_message(parent, "hints.frame_configure_hints", {
index: parent_indexes[0],
parent_indexes: parent_indexes.slice(1),
labels: labels.slice(label_index),
});
} else {
// hints are ready
post_webmacs_message("_browserObjectActivated", ["null"]);
}
}
// alphabet only
frame_configure_hints(args) {
this.configure_hints(args.index, args.labels, args.parent_indexes);
}
selectBrowserObjects(selector, method, options) {
this.init(selector, method, JSON.parse(options));
this.lookup(0);
if (method === "filter") {
this.activateNextHint(false);
}
}
clearBrowserObjects() {
// has been cleared.
if (this.hints === null) {
return;
}
for (var hint of this.hints) {
hint.remove();
}
this.hints = null;
}
frameUpActivateHint(indexes) {
let hint = null;
if (indexes !== null) {
let index = indexes.shift();
hint = this.hints[index];
}
let prev = this.activeHint;
if (hint === prev) {
return hint;
}
this.clearFrameSelection();
this.activeHint = hint;
if (top != self) {
post_message(parent, "hints.frameUpActivateHint", indexes);
}
return hint;
}
clearFrameSelection() {
let prevHint = this.activeHint;
if (! prevHint) {
return;
}
this.activeHint = null;
if (prevHint instanceof BaseHint) {
prevHint.refresh();
} else {
post_message(prevHint.frame.contentWindow,
"hints.clearFrameSelection");
}
}
setCurrentActiveHint(indexes) {
let hint = this.frameUpActivateHint(indexes);
if (hint) {
// refresh the hint style to make it appear activated.
hint.refresh();
post_webmacs_message("_browserObjectActivated", [hint.serialize()]);
}
}
frameActivateNextHint(args) {
if (this.method !== "filter") {
return;
}
let traverse = function(hinter, index) {
let hint = hinter.hints[index];
if (hint instanceof BaseHint) {
if (hint.isVisible()) {
hinter.setCurrentActiveHint([index].concat(args.parent_indexes));
return true;
}
} else {
let parent_indexes = args.parent_indexes;
// this is a hint frame, so go down that frame
post_message(hint.frame.contentWindow,
"hints.frameActivateNextHint", {
// a list of ordered indexes of the calling
// frame hint
parent_indexes: [index].concat(args.parent_indexes),
way: args.way
});
return true;
}
return false;
};
let index = args.index;
if (index === undefined) {
if (this.activeHint) {
// just go down on the current hint if it is a frame
index = this.hints.indexOf(this.activeHint);
// else, activate the next hint
if (this.activeHint instanceof BaseHint) {
index += args.way;
}
} else {
// if no selection, select the first one
index = args.way == 1 ? 0: this.hints.length - 1;
}
}
if (args.way === 1) {
for (; index < this.hints.length; index++) {
if (traverse(this, index)) {
return;
}
}
} else {
for (; index >= 0; index--) {
if (traverse(this, index)) {
return;
}
}
}
// we found no selection if we are here
if (self !== top) {
// recall the parent frame
post_message(parent, "hints.frameActivateNextHint", {
index: args.parent_indexes[0] + args.way,
way: args.way
});
} else {
// on the main frame, clear the selection and recall this function
// to loop.
if (this.hints) {
this.setCurrentActiveHint(null);
this.frameActivateNextHint({way: args.way, parent_indexes: []});
}
}
}
activateNextHint(backward) {
this.frameActivateNextHint({
way: backward ? -1 : 1,
parent_indexes: [],
});
}
followCurrentLink() {
if (this.activeHint) {
if (this.activeHint instanceof BaseHint) {
clickLike(this.activeHint.obj);
} else {
post_message(this.activeHint.frame.contentWindow,
"hints.followCurrentLink");
}
}
}
frameSelectVisibleHint(args) {
let frameHint = null;
let index = args.index;
for (let hint_index=0; hint_index < this.hints.length; hint_index++) {
let hint = this.hints[hint_index];
if (hint instanceof BaseHint) {
if (! hint.isVisible()) {
continue;
}
let nb = parseInt(hint.hint.textContent);
if (nb === index) {
this.setCurrentActiveHint([hint_index].concat(args.parent_indexes));
return;
} else if (nb > index) {
break;
}
} else {
frameHint = {window: hint.frame.contentWindow, index: hint_index};
}
}
if (frameHint) {
post_message(
frameHint.window,
"hints.frameSelectVisibleHint", {
index: index,
parent_indexes: [frameHint.index].concat(args.parent_indexes)
}
);
}
}
selectVisibleHint(index) {
if (this.method !== "filter") {
return;
}
this.frameSelectVisibleHint({index: parseInt(index), parent_indexes: []});
}
frameFilterSelection(args) {
let hint_index = args.hint_index;
// match everything when text selector is empty
let match_hint = hint => true;
if (args.text) {
if (this.method == "filter") {
// fuzzy-match on the hint text
let parts = args.text.split(/\s+/).map(escapeRegExp);
let re = new RegExp(".*" + parts.join(".*") + ".*", "i");
match_hint = function(hint) {
let text = hint.text();
if (text !== null) {
return (text.match(re) !== null);
}
return false;
};
} else {
match_hint = function(hint) {
return hint.chars.startsWith(args.text);
};
}
}
for (let index = args.index; index < this.hints.length; index++) {
let hint = this.hints[index];
if (hint instanceof HintFrame) {
// iframe, let's go down
post_message(hint.frame.contentWindow, "hints.frameFilterSelection", {
text: args.text,
index: 0,
parent_indexes: [index].concat(args.parent_indexes),
hint_index: hint_index
});
return;
}
// else see if we match the hint, and update its visibility
if (match_hint(hint)) {
hint_index +=1;
hint.setVisible(true);
if (this.method == "filter") {
hint.hint.textContent = hint_index;
} else {
if (hint.chars == args.text) {
// alphabet always set the active hint when we found the
// final hint.
this.setCurrentActiveHint(
[index].concat(args.parent_indexes)
);
return;
}
}
} else {
hint.setVisible(false);
if (hint == this.activeHint) {
this.setCurrentActiveHint(null);
}
}
}
if (self !== top) {
// if we are in a sub frame, we call back the parent so he will
// continue.
post_message(parent, "hints.frameFilterSelection", {
text: args.text,
index: args.parent_indexes[0] + 1,
hint_index: hint_index,
parent_indexes: args.parent_indexes.slice(1)
});
} else {
// else if we lose the selection, put it back to the first hint.
if (this.activeHint === null && this.method == "filter") {
this.activateNextHint(false);
}
}
}
filterSelection(text) {
this.frameFilterSelection({
text: text,
index: 0,
hint_index: 0,
parent_indexes: [],
});
}
}
var hints = new Hinter();
function currentLinkUrl() {
let elt = document.activeElement;
if (elt.tagName == "A") {
post_webmacs_message("currentLinkUrl", [elt.href]);
} else if (elt.tagName == "IFRAME") {
post_message(elt.contentWindow, "hints.foundCurrentLinkUrl", null);
} else {
post_webmacs_message("currentLinkUrl", [""]);
}
}
if (self !== top) {
register_message_handler("hints.lookup_in_iframe_start", function(args) {
hints.init(args.selector, args.method, args.options);
hints.lookup(args.hint_index);
});
register_message_handler("hints.select_clear",
_ => hints.clearBrowserObjects());
register_message_handler("hints.clearFrameSelection",
_ => hints.clearFrameSelection());
register_message_handler("hints.frameSelectVisibleHint",
args => hints.frameSelectVisibleHint(args));
register_message_handler("hints.foundCurrentLinkUrl",
_ => currentLinkUrl());
}
register_message_handler("hints.lookup_in_iframe_end",
hint_index => hints.lookup(hint_index));
register_message_handler("hints.frameActivateNextHint",
args => hints.frameActivateNextHint(args));
register_message_handler("hints.followCurrentLink",
_ => hints.followCurrentLink());
register_message_handler("hints.frameFilterSelection",
args => hints.frameFilterSelection(args));
register_message_handler("hints.frameUpActivateHint",
args => hints.frameUpActivateHint(args));
register_message_handler("hints.frame_configure_hints",
args => hints.frame_configure_hints(args));
================================================
FILE: webmacs/scripts/password_manager.js
================================================
// This file is part of webmacs.
//
// webmacs is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// webmacs is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with webmacs. If not, see .
var password_manager = {};
password_manager.create_fake_event = function(typeArg) {
if (['keydown', 'keyup', 'keypress'].includes(typeArg)) {
return new KeyboardEvent(typeArg, {
'key': ' ',
'code': ' ',
'charCode': ' '.charCodeAt(0),
'keyCode': ' '.charCodeAt(0),
'which': ' '.charCodeAt(0),
'bubbles': true,
'composed': true,
'cancelable': true
});
} else if (['input', 'change'].includes(typeArg)) {
return new InputEvent(typeArg, {
'bubbles': true,
'composed': true,
'cancelable': true
});
} else if (['focus', 'blur'].includes(typeArg)) {
return new FocusEvent(typeArg, {
'bubbles': true,
'composed': true,
'cancelable': true
});
} else {
console.log.error("password_manager.create_fake_event: Unknown event type: " + typeArg);
return null;
}
}
password_manager.set_input_value = function(input, value) {
input.value = value;
for (let action of ['focus', 'keydown', 'keyup', 'keypress',
'input', 'change', 'blur']) {
input.dispatchEvent(password_manager.create_fake_event(action));
input.value = value;
}
}
password_manager.allowedInputTypeForName = ['text', 'email', 'tel'];
password_manager.complete_form_data = function(data) {
var allowedUserAttrib = ["name", "id", "autocomplete", "placeholder"];
var allowedInputTypeForName = ['text', 'email', 'tel'];
var inputs = document.getElementsByTagName('input');
var focusedElem = document.activeElement;
for (var i=0; i < inputs.length; ++i) {
var input = inputs[i];
// don't fill if element is invisible
if (input.offsetHeight === 0 || input.offsetParent === null) {
continue;
}
var type = input.type.toLowerCase();
if (data.password !== null && type === 'password') {
password_manager.set_input_value(input, data.password);
} else if (data.username !== null && password_manager.allowedInputTypeForName.includes(type)) {
password_manager.set_input_value(input, data.username);
}
for (const j in allowedUserAttrib) {
var attribName = allowedUserAttrib[j];
var attrib = input.getAttribute(attribName);
for (const field in data.fields) {
if (attrib == field) {
password_manager.set_input_value(input, data.fields[field]);
break;
}
}
}
}
if (focusedElem) {
focusedElem.focus(); // get back the focus
}
}
================================================
FILE: webmacs/scripts/setup.js
================================================
// This file is part of webmacs.
//
// webmacs is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// webmacs is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with webmacs. If not, see .
// per frame handlers for the message sent with window.postMessage.
let MESSAGE_HANDLERS = {};
/*
Use window.postMessage to send a cross-origin message.
This allow to bypass the same origin policy in frames. An id is injected
in the message sent, to be more secure.
w: the window to send to.
name: the message name.
args: the message parameters.
*/
function post_message(w, name, args) {
w.postMessage({
webmacsid: WEBMACS_SECURE_ID,
name: name,
args: args
}, "*");
}
// register sent messages on each frame.
window.addEventListener("message", function(e) {
let data = e.data;
if (typeof(data) == "object"
&& data.webmacsid === WEBMACS_SECURE_ID) {
let handler = MESSAGE_HANDLERS[data.name];
if (handler !== undefined) {
handler(data.args);
}
}
})
/*
register a message handler in the current frame.
*/
function register_message_handler(name, func) {
MESSAGE_HANDLERS[name] = func;
}
/*
Post a message on the webmacs python side, using the qtwebchannel.
This use post_message if we are not on the main_frame, as only the main frame
posses the __webmacsHandler__ web channel object.
*/
function post_webmacs_message(name, args) {
if (self === top) {
__webmacsHandler__[name].apply(__webmacsHandler__, args);
} else {
post_message(top, name, args);
}
}
function isTextInput(node) {
return node.isContentEditable
|| node.nodeName == "INPUT" || node.nodeName == "TEXTAREA";
}
// register focus in and out event on each frame.
document.addEventListener("focusin", function(e) {
if (isTextInput(e.target)) {
post_webmacs_message("onTextFocus", [true]);
}
}, true);
document.addEventListener("focusout", function(e) {
if (isTextInput(e.target)) {
post_webmacs_message("onTextFocus", [false]);
}
}, true);
if (self === top) {
// for post_webmacs_message to works when called from iframes.
register_message_handler("onTextFocus",
args => post_webmacs_message("onTextFocus", args));
register_message_handler("copyToClipboard",
args => post_webmacs_message("copyToClipboard",
args));
register_message_handler("openExternalEditor",
args => post_webmacs_message("openExternalEditor",
args));
register_message_handler("onCaretBrowsing",
args => post_webmacs_message("onCaretBrowsing",
args));
register_message_handler("currentLinkUrl",
args => post_webmacs_message("currentLinkUrl",
args));
register_message_handler("_browserObjectActivated",
args => post_webmacs_message("_browserObjectActivated",
args));
// and now, register the web channel on the top frame.
function registerWebmacs(w) {
// only called in main frame
console.log("registering...");
window.__webmacsHandler__ = w;
// force the focus on the current web content
post_webmacs_message(
"onTextFocus",
[!!(document.activeElement && isTextInput(document.activeElement))]
);
var event = document.createEvent('Event');
event.initEvent('_webmacs_external_created', true, true);
document.dispatchEvent(event);
}
function registerWebChannel() {
try {
new QWebChannel(
qt.webChannelTransport,
channel => registerWebmacs(channel.objects.contentHandler)
);
} catch (e) {
setTimeout(registerWebChannel, 50);
}
}
registerWebChannel();
}
================================================
FILE: webmacs/scripts/textedit.js
================================================
// This file is part of webmacs.
//
// webmacs is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// webmacs is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with webmacs. If not, see .
var textedit = {};
textedit.EXTERNAL_EDITOR_REQUESTS = {};
textedit.clear_mark = function() {
let elt = document.activeElement;
if (elt.tagName == "IFRAME") {
post_message(elt.contentWindow, "textedit.clear_mark", null);
} else {
if (! elt.isContentEditable) {
var pos = elt.selectionDirection == "forward" ? elt.selectionEnd : elt.selectionStart;
elt.setSelectionRange(pos, pos);
} else {
let sel = document.getSelection();
if (! sel.isCollapsed) {
sel.collapse(sel.focusNode, sel.focusOffset);
}
}
}
}
textedit.blur = function() {
let elt = document.activeElement;
if (elt.tagName == "IFRAME") {
post_message(elt.contentWindow, "textedit.blur", null);
} else {
elt.blur();
}
}
textedit.copy_text = function(reset_selection) {
let elt = document.activeElement;
if (elt.tagName == "IFRAME") {
post_message(elt.contentWindow, "textedit.copy_text", null);
} else {
let sel = document.getSelection();
if (sel.type !== 'Range') {
return;
}
post_webmacs_message("copyToClipboard", [sel.toString()]);
if (reset_selection) {
textedit.clear_mark();
}
}
}
textedit.select_text = function(direction, granularity) {
let elt = document.activeElement;
if (elt.tagName == "IFRAME") {
post_message(elt.contentWindow, "textedit.select_text", [direction, granularity]);
} else {
textedit.clear_mark();
if (!direction && !granularity) {
elt.select();
} else {
document.getSelection().modify("extend", direction, granularity);
}
}
}
textedit._change_next_word_case = function(fn) {
let elt = document.activeElement;
textedit.select_text('forward', 'word');
if (elt.isContentEditable) {
return;
}
var pos = elt.selectionStart;
var txt = elt.value;
var nextpos = elt.selectionEnd;
elt.value = txt.slice(0, pos) + fn(txt.slice(pos, nextpos))
+ txt.slice(nextpos);
elt.setSelectionRange(nextpos, nextpos);
}
textedit.upcase_word = function() {
let elt = document.activeElement;
if (elt.tagName == "IFRAME") {
post_message(elt.contentWindow, "textedit.upcase_word", null);
} else {
textedit._change_next_word_case(function(t) {
return t.toUpperCase();
});
}
}
textedit.downcase_word = function() {
let elt = document.activeElement;
if (elt.tagName == "IFRAME") {
post_message(elt.contentWindow, "textedit.downcase_word", null);
} else {
textedit._change_next_word_case(function(t) {
return t.toLowerCase();
});
}
}
textedit.capitalize_word = function() {
let elt = document.activeElement;
if (elt.tagName == "IFRAME") {
post_message(elt.contentWindow, "textedit.capitalize_word", null);
} else {
textedit._change_next_word_case(function(t) {
return t.toLowerCase().replace(/(?:^|\s)\S/g, function(a) {
return a.toUpperCase();
});
});
}
}
textedit.external_editor_open = function() {
let elt = document.activeElement;
if (elt.tagName == "IFRAME") {
post_message(elt.contentWindow, "textedit.external_editor_open", null);
} else {
var id = new Date().getUTCMilliseconds() + "";
let txt = (elt.isContentEditable) ?
elt.innerText : elt.value;
post_webmacs_message("openExternalEditor", [id, txt]);
textedit.EXTERNAL_EDITOR_REQUESTS[id] = elt;
}
}
textedit.external_editor_finish = function(id, content) {
let elt = document.activeElement;
if (elt.tagName == "IFRAME") {
post_message(elt.contentWindow, "textedit.external_editor_finish", [id, content]);
} else {
textedit._external_editor_finish([id, content]);
}
}
textedit._external_editor_finish = function(args) {
let id = args[0];
let content = args[1];
if (content !== false) {
let e = textedit.EXTERNAL_EDITOR_REQUESTS[id];
if (e.isContentEditable) {
e.innerText = content;
} else {
e.value = content;
}
}
delete textedit.EXTERNAL_EDITOR_REQUESTS[id];
}
if (self !== top) {
register_message_handler("textedit.clear_mark", textedit.clear_mark);
register_message_handler("textedit.blur", textedit.blur);
register_message_handler("textedit.copy_text", textedit.copy_text);
register_message_handler("textedit.select_text", textedit.select_text);
register_message_handler("textedit.upcase_word", textedit.upcase_word);
register_message_handler("textedit.downcase_word", textedit.downcase_word);
register_message_handler("textedit.external_editor_open",
textedit.external_editor_open);
register_message_handler("textedit.external_editor_finish",
textedit._external_editor_finish);
}
================================================
FILE: webmacs/scripts/textzoom.js
================================================
var textzoom = {};
textzoom.totalRatio = 1;
textzoom.IGNORED_TAGS = /SCRIPT|NOSCRIPT|LINK|BR|EMBED|IFRAME|IMG|VIDEO|CANVAS|STYLE/;
textzoom.multiplyByRatio = function(value, multiplier) {
return (parseFloat(value) * multiplier) + 'px';
};
textzoom.addImportantStyle = function(el, name, value) {
return el.style.cssText += name + ": " + value + " !important;";
};
textzoom.isBlank = function(str) {
return !str || /^\s*$/.test(str)
}
textzoom.resetChangeFont = function() {
for (let i=0; i.
import json
import logging
from .import BUFFERS, windows, current_window
from .webbuffer import create_buffer, QUrl, DelayedLoadingUrl, close_buffer
from .window import Window
FORMAT_VERSION = 2
def _session_load(stream):
data = json.load(stream)
version = data.get("version", 0)
urls = data["urls"]
if version < 2:
urls = reversed(urls)
# apply the session config
# now, load urls in buffers
for url in urls:
if isinstance(url, str):
# old format, no delay loading support
# TODO must be removed after some time
create_buffer(url)
else:
# new format, url must be a dict
buff = create_buffer(DelayedLoadingUrl(
url=QUrl(url["url"]),
title=url["title"]
))
if version >= 2:
buff.last_use = url["last_use"]
if version > 0:
def create_window(wdata):
win = Window()
win.restore_state(wdata, version)
win.show()
current_index = data.get("current-window", 0)
for i, wdata in enumerate(data["windows"]):
if i != current_index:
create_window(wdata)
# create the current last, so it has focus and is on top
create_window(data["windows"][current_index])
else:
cwin = Window()
cwin.showMaximized()
# and open the first buffer in the view
if BUFFERS:
cwin.current_webview().setBuffer(BUFFERS[0])
def _session_save(stream):
urls = [{
"url": b.url().toString(),
"title": b.title(),
"last_use": b.last_use,
} for b in BUFFERS]
json.dump({
"version": FORMAT_VERSION,
"urls": urls,
"windows": [w.dump_state() for w in windows()],
"current-window": windows().index(current_window()),
}, stream)
def session_clean():
# clean every opened buffers and windows
for window in windows():
window.quit_if_last_closed = False
window.close()
for view in window.webviews():
view.setBuffer(None)
for buffer in BUFFERS:
close_buffer(buffer)
def session_load(session_file):
"""
Try to load the session, given the profile.
Must be called at application startup, when no buffers nor views is set up
already.
"""
if session_file is None:
return
try:
with open(session_file, "r") as f:
_session_load(f)
except Exception:
logging.exception("Unable to load the session from %s.",
session_file)
session_clean()
raise
def session_save(session_file):
if session_file is None:
return
"""
Save the session for the given profile.
"""
with open(session_file, "w") as f:
_session_save(f)
================================================
FILE: webmacs/spell_checking.py
================================================
# This file is part of webmacs.
#
# webmacs is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# webmacs is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with webmacs. If not, see .
from . import variables
from .task import Task
from PyQt6.QtNetwork import QNetworkRequest, QNetworkReply
from PyQt6.QtCore import QUrl
import os
import re
import json
import logging
import base64
from collections import namedtuple
RemoteBdic = namedtuple("RemoteBdic", ("name", "version", "extension"))
BDIC_FILES_URL = "https://chromium.googlesource.com/chromium/deps/hunspell_dictionaries.git/+/master/" # noqa
spell_checking_dictionaries = variables.define_variable(
"spell-checking-dictionaries",
"List of dictionaries to use for spell checking.",
(),
type=variables.List(variables.String()),
)
class Versions(dict):
@classmethod
def from_file(cls, path):
if os.path.isfile(path):
with open(path) as f:
return cls(**json.load(f))
else:
return cls()
def get_version(self, name):
return self.get(name, [0, 0])
def write(self, path):
with open(path, "w") as f:
json.dump(dict(**self), f)
class SpellCheckingTask(Task):
def __init__(self, app, path):
Task.__init__(self)
self.app = app
self.path = path
self.__versions_path = os.path.join(self.path, "versions.json")
self.__versions = None
self.__modified = False
self.__bdic_replies = {}
def start(self):
if not os.path.isdir(self.path):
try:
os.makedirs(self.path)
except Exception as exc:
logging.warning("Can not initialize spell checking dir %s: %s"
% (self.path, exc))
return False
self.__versions = Versions.from_file(self.__versions_path)
self.__bdic_replies.clear()
reply = self.app.network_manager.get(
QNetworkRequest(QUrl(BDIC_FILES_URL + "?format=json")))
reply.finished.connect(self._on_bdic_files_list)
def _on_bdic_files_list(self):
reply = self.sender()
# A special 5-byte prefix must be stripped from the response
# See: https://github.com/google/gitiles/issues/22
# https://github.com/google/gitiles/issues/82
json_data = json.loads(bytes(reply.readAll())[5:].decode("utf-8"))
re_bdic = re.compile(r"(.*)-(\d+-\d+)(\.bdic)$")
remotes = {}
for entry in json_data["entries"]:
res = re_bdic.match(entry["name"])
if res:
name, version, extension = (res.group(1), res.group(2),
res.group(3))
version = [int(e) for e in version.split("-")]
remotes[name] = RemoteBdic(name, version, extension)
local_files = set(os.path.splitext(f)[0] for f in os.listdir(self.path)
if f.endswith(".bdic"))
for name in spell_checking_dictionaries.value:
try:
r = remotes[name]
except KeyError:
logging.warning(
"No spell checking dict for %s. \nAvailable: %s"
% (name, list(remotes))
)
continue
if name not in local_files:
logging.info("Downloading dict %s" % name)
self._install(r)
elif r.version > self.__versions.get(name, [0, 0]):
logging.info("Updating dict %s" % name)
self._install(r)
if not self.__bdic_replies:
self.finished.emit()
def _install(self, r):
url = "{}{}-{}{}?format=TEXT".format(
BDIC_FILES_URL,
r.name,
"-".join(str(v) for v in r.version),
r.extension
)
dest = os.path.join(self.path, "{}{}".format(r.name, r.extension))
reply = self.app.network_manager.get(QNetworkRequest(QUrl(url)))
self.__bdic_replies[reply] = {"bdic": r, "dest": dest, "data": b""}
reply.readyRead.connect(self._bdic_ready_read)
reply.finished.connect(self._bdic_finished)
def _bdic_ready_read(self):
reply = self.sender()
self.__bdic_replies[reply]["data"] += bytes(reply.readAll())
def _bdic_finished(self):
reply = self.sender()
data = self.__bdic_replies.pop(reply)
if reply.error() != QNetworkReply.NetworkError.NoError:
if not self.error():
self.set_error_message(reply.errorString())
# abort other replies
for r in self.__bdic_replies:
r.abort()
if not self.__bdic_replies:
self.finished.emit()
return
with open(data["dest"], 'bw') as f:
f.write(base64.decodebytes(data["data"]))
self.__versions[data["bdic"].name] = data["bdic"].version
if not self.__bdic_replies:
self.__versions.write(self.__versions_path)
self.finished.emit()
================================================
FILE: webmacs/task.py
================================================
# This file is part of webmacs.
#
# webmacs is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# webmacs is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with webmacs. If not, see .
import logging
from PyQt6.QtCore import QObject, pyqtSignal as Signal, pyqtSlot as Slot
from . import call_later
class Task(QObject):
"""
A long running task.
Note it is designed to be asynchronous - using signals, and if any thread
is needed, QThreadPool might be used but each subclass must deal with
concurrency.
"""
finished = Signal()
description = None
def __init__(self):
QObject.__init__(self)
self.__error = None
@Slot()
def start(self):
"""
Start the task.
"""
@Slot()
def abort(self):
"""
Abort the task.
"""
def error(self):
"""
True if there was an error.
"""
return bool(self.__error)
def error_message(self):
"""
Description of the error if any.
"""
return self.__error
def set_error(self, message):
self.__error = message
def __str__(self):
return self.description or self.__class__.__name__
class TaskRunner(QObject):
def __init__(self):
QObject.__init__(self)
self.running_tasks = set()
def run(self, task):
"""
Run the task in the next main loop iteration.
"""
task.finished.connect(self._on_task_finished)
call_later(task.start)
logging.info(f"Starting task {task}")
self.running_tasks.add(task)
@Slot()
def stop(self):
for task in self.running_tasks:
task.abort()
def _on_task_finished(self):
task = self.sender()
if not task.error():
logging.info(f"Task {task}: finished.")
else:
logging.error(f"Task {task}: Error {task.error_message()}")
self.running_tasks.remove(task)
================================================
FILE: webmacs/url_opener.py
================================================
# This file is part of webmacs.
#
# webmacs is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# webmacs is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with webmacs. If not, see .
from .webbuffer import create_buffer
from .window import Window
def url_open(ctx, url, instance=None, new_window=False, new_buffer=False):
"""
Open an url.
"""
assert instance is None # TODO FIXME implement later
buffer = None
if new_buffer or new_window:
buffer = create_buffer()
if new_window:
w = Window()
w.current_webview().setBuffer(buffer)
buffer.load(url) # load before showing
w.show()
w.activateWindow()
return
else:
ctx.view.setBuffer(buffer)
else:
buffer = ctx.buffer
buffer.load(url)
================================================
FILE: webmacs/variables.py
================================================
VARIABLES = {}
class VariableConditionError(Exception):
"Raised when a variable condition is not fulfilled"
def condition(func, doc):
def _condition(value):
if not func(value):
assert False, doc
_condition.__doc__ = doc
return _condition
class Variable(object):
__slots__ = ("name", "doc", "value", "conditions", "callbacks", "type")
def __init__(self, name, doc, value, conditions=(), callbacks=(),
type=None):
self.name = name
self.doc = doc
self.conditions = conditions
assert isinstance(type, Type)
self.type = type
self.validate(value)
self.value = value
self.callbacks = callbacks
def validate(self, value):
self.type.validate(value)
for cond in self.conditions:
try:
cond(value)
except Exception as exc:
raise VariableConditionError(
"Condition failed for variable {} with value {}: {}"
.format(self.name, repr(value), exc)
) from None
def set_value(self, value):
if value != self.value:
self.validate(value)
self.value = value
for callback in self.callbacks:
callback(self)
def add_callback(self, callback):
if not isinstance(self.callbacks, list):
self.callbacks = list(self.callbacks)
self.callbacks.append(callback)
class Type(object):
def validate(self, value):
pass
def _describe(self, result):
pass
def type_name(self):
return ""
def describe(self):
result = [self.type_name(), []]
self._describe(result[1])
return result
class ChoiceMixin(object):
def __init__(self, choices=None, **kwargs):
super().__init__(**kwargs)
self.choices = choices
def validate(self, value):
super().validate(value)
if self.choices and value not in self.choices:
raise VariableConditionError("Must be one of %r"
% (tuple(self.choices),))
def _describe(self, result):
if self.choices:
result.append("one of %r" % (tuple(self.choices),))
super()._describe(result)
class RangeMixin(object):
def __init__(self, min=None, max=None, **kwargs):
super().__init__(**kwargs)
self.min = min
self.max = max
def validate(self, value):
super().validate(value)
if self.min is not None and value < self.min:
raise VariableConditionError(
"Must be greater or equal to %s" % self.min
)
if self.max is not None and value > self.max:
raise VariableConditionError(
"Must be lesser or equal to %s" % self.max
)
def _describe(self, result):
if self.min is not None:
result.append(">= %s" % self.min)
if self.max is not None:
result.append("<= %s" % self.max)
super()._describe(result)
class String(ChoiceMixin, Type):
def validate(self, value):
if not isinstance(value, str):
raise VariableConditionError("Must be a string")
super().validate(value)
def type_name(self):
return "String"
class Int(ChoiceMixin, RangeMixin, Type):
def validate(self, value):
if not isinstance(value, int):
raise VariableConditionError("Must be an integer")
super().validate(value)
def type_name(self):
return "Int"
class Float(ChoiceMixin, RangeMixin, Type):
def validate(self, value):
if not isinstance(value, float):
raise VariableConditionError("Must be a float")
super().validate(value)
def type_name(self):
return "Float"
class Bool(Type):
def validate(self, value):
if not isinstance(value, bool):
raise VariableConditionError("Must be True or False")
def type_name(self):
return "Bool"
class List(Type):
def __init__(self, type):
self.type = type
def validate(self, value):
if not isinstance(value, (tuple, list)):
raise VariableConditionError("Must be a list")
for i, v in enumerate(value):
try:
self.type.validate(v)
except VariableConditionError as exc:
raise VariableConditionError("List at position %d: %s"
% (i, exc)) from None
def type_name(self):
return "List"
def describe(self):
result = super().describe()
result.append({"of": self.type.describe()})
return result
class Tuple(Type):
def __init__(self, *types):
self.types = types
def validate(self, value):
if not (isinstance(value, (tuple, list))
and len(self.types) == len(value)):
raise VariableConditionError("Must be a tuple of size %d"
% len(self.types))
for i, v in enumerate(value):
try:
self.types[i].validate(value[i])
except VariableConditionError as exc:
raise VariableConditionError("Tuple at position %d: %s"
% (i, exc)) from None
def type_name(self):
return "Tuple"
def describe(self):
result = super().describe()
for i, t in enumerate(self.types):
result.append({"at index %i" % i: t.describe()})
return result
class Dict(Type):
def __init__(self, key_type, value_type):
self.key_type = key_type
self.value_type = value_type
def validate(self, value):
if not (isinstance(value, dict)):
raise VariableConditionError("Must be a dict")
for k, v in value.items():
try:
self.key_type.validate(k)
except VariableConditionError as exc:
raise VariableConditionError("Key %r: %s" % (k, exc))
try:
self.value_type.validate(v)
except VariableConditionError as exc:
raise VariableConditionError("Value for key %r: %s" % (k, exc))
def type_name(self):
return "Dict"
def describe(self):
result = super().describe()
result.append({"key": self.key_type.describe()})
result.append({"value": self.key_type.describe()})
return result
def define_variable(name, doc, value, **kwargs):
var = Variable(name, doc, value, **kwargs)
VARIABLES[name] = var
return var
def get_variable(name):
try:
return VARIABLES[name]
except KeyError:
raise KeyError("No such variable %s" % name)
def get(name):
"""
Returns the variable value.
:param name: the name of the variable.
"""
return get_variable(name).value
def set(name, value):
"""
Set a value for a variable.
:param name: the name of the variable.
:param value: the new value.
:raises: KeyError if the variable does not exists, or
:class:`VariableConditionError` if the value is incorrect.
"""
get_variable(name).set_value(value)
define_variable(
"home-page",
"Defines the url to use when webmacs starts. If set to the empty"
" string, the last session will be loaded. You can set it to"
" 'about:blank' if you want an empty page.",
"",
type=String(),
)
define_variable(
"home-page-in-new-window",
"Use the home page when creating a new window with *make-window*."
" Default to False.",
False,
type=Bool()
)
================================================
FILE: webmacs/version.py
================================================
# This file is part of webmacs.
#
# webmacs is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# webmacs is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with webmacs. If not, see .
import os
import sys
import logging
import re
import subprocess
from PyQt6.QtWebEngineCore import QWebEngineProfile
from PyQt6.QtCore import (QT_VERSION_STR, PYQT_VERSION_STR, # noqa: F401
QT_VERSION, PYQT_VERSION)
from . import __version__ as WEBMACS_VERSION_STR # noqa: F401
is_mac = sys.platform.startswith('darwin')
is_linux = sys.platform.startswith('linux')
is_windows = sys.platform.startswith('win')
is_posix = os.name == 'posix'
# this is required as we mock extension modules to generate the doc and
# sometimes that needs manual work for autodoc to works well.
building_doc = "sphinx" in sys.modules
def QT_VERSION_CHECK(major, minor=0, patch=0):
return (major << 16) | (minor << 8) | patch
class _QtVersionChecker(object):
__slots__ = ("version",)
def __init__(self, version):
self.version = version
def __eq__(self, other):
return self.version == QT_VERSION_CHECK(*other)
def __lt__(self, other):
return self.version < QT_VERSION_CHECK(*other)
def __gt__(self, other):
return self.version > QT_VERSION_CHECK(*other)
def __le__(self, other):
return self.version <= QT_VERSION_CHECK(*other)
def __ge__(self, other):
return self.version >= QT_VERSION_CHECK(*other)
min_qt_version = _QtVersionChecker(min(QT_VERSION, PYQT_VERSION))
qt_version = _QtVersionChecker(QT_VERSION)
pyqt_version = _QtVersionChecker(PYQT_VERSION)
def chromium_version():
"""
Get the Chromium version for QtWebEngine.
This can also be checked by looking at this file with the right Qt tag:
https://github.com/qt/qtwebengine/blob/dev/tools/scripts/version_resolver.py#L41
"""
if QWebEngineProfile is None:
# This should never happen
return 'unavailable'
profile = QWebEngineProfile()
ua = profile.httpUserAgent()
match = re.search(r' Chrome/([^ ]*) ', ua)
if not match:
logging.error("Could not get Chromium version from: {}".format(ua))
return 'unknown'
return match.group(1)
def webmacs_revision():
"""
Try to get webmacs git revision.
First try to read the "revision" file that should be created at
installation time - else fall back to executing the git command.
"""
path = os.path.dirname(sys.modules["webmacs"].__file__)
revision_file = os.path.join(path, "revision")
if os.path.isfile(revision_file):
with open(revision_file) as f:
return f.read().strip()
# git directory should be in the parent directory
if not os.path.exists(os.path.join(os.path.dirname(path), ".git")):
return None
p = subprocess.Popen(
["git", "rev-parse", "HEAD"], cwd=path,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
)
out, err = p.communicate()
if p.returncode == 0:
return out.strip().decode("utf-8")
================================================
FILE: webmacs/visited_links.py
================================================
# This file is part of webmacs.
#
# webmacs is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# webmacs is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with webmacs. If not, see .
import sqlite3
from datetime import datetime
from . import variables
visited_links_display_limit = variables.define_variable(
"visited-links-display-limit",
"Limit the number of history elements displayed in the"
" visited-links-history command.",
2000,
type=variables.Int(min=1)
)
class VisitedLinks(object):
def __init__(self, dbbath):
self._conn = sqlite3.connect(dbbath)
self._conn.execute("""
CREATE TABLE IF NOT EXISTS visitedlinks
(url TEXT PRIMARY KEY, title TEXT, lastseen DATE);
""")
def visit(self, url, title):
self._conn.execute("""
INSERT OR REPLACE INTO visitedlinks (url, title, lastseen)
VALUES (?, ?, ?)
""", (url, title, datetime.now()))
self._conn.commit()
def visited_urls(self):
return [(row[0], row[1]) for row in self._conn.execute(
"select url, title from visitedlinks order by lastseen DESC"
" LIMIT %d" % visited_links_display_limit.value
)]
def remove(self, url):
self._conn.execute("""
DELETE from visitedlinks WHERE url = ?
""", (url,))
================================================
FILE: webmacs/webbuffer.py
================================================
# This file is part of webmacs.
#
# webmacs is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# webmacs is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with webmacs. If not, see .
import logging
import time
import json
from PyQt6.QtCore import QUrl, pyqtSlot as Slot
from PyQt6.QtWebEngineCore import QWebEnginePage, QWebEngineScript
from PyQt6.QtWebChannel import QWebChannel
from collections import namedtuple
from . import hooks, variables, windows
from . import BUFFERS, current_minibuffer, minibuffer_show_info, \
current_buffer, call_later, current_window, recent_buffers
from .content_handler import WebContentHandler
from .application import app
from .minibuffer.prompt import YesNoPrompt, AskPasswordPrompt
from .password_manager import PasswordManagerNotReady
from .keyboardhandler import LOCAL_KEYMAP_SETTER
from .mode import get_mode, Mode, get_auto_modename_for_url
close_buffer_close_window = variables.define_variable(
"close-buffer-close-window",
"Policy to close a window when the last available buffer is closed."
" If never, closing a buffer will never close a window."
" If all, closing a buffer can close a window, even the last one ("
" and so it will exit the application). Finally, all-but-last is like"
" all but the last window will never be closed.",
"never",
type=variables.String(choices=("never", "all", "all-but-last")),
)
# a tuple of QUrl, str to delay loading of a page.
DelayedLoadingUrl = namedtuple("DelayedLoadingUrl", ("url", "title"))
def close_buffer(wb):
view = wb.view()
if view:
# buffer is currently visible, search for a buffer that is not visible
# yet to put it in the view
invisibles = [b for b in recent_buffers() if not b.view()]
if not invisibles:
if len(view.main_window.webviews()) > 1:
# we can close the current view if it is not alone
view.main_window.close_view(view)
else:
close_window = close_buffer_close_window.value
if close_window == "never":
# never close the buffer, nor the window
return
elif len(windows()) == 1:
# if only one window left and policy is all, quit the app
if close_window == "all":
app().quit()
return
else:
# close both the window and the buffer
view.setBuffer(None)
view.main_window.close()
else:
# associate the first buffer that does not have any view yet
view.setBuffer(invisibles[0])
internal_view = wb.internal_view()
if internal_view:
# remove the associated internal page view (might be causing a crash
# from when calling ~QWebEnginePage())
internal_view.deleteLater()
# clear the web channel. Might be causing a crash when calling
# ~QWebEnginePage()
wb.webChannel().deleteLater()
wb.setWebChannel(None)
BUFFERS.remove(wb)
wb.deleteLater()
hooks.webbuffer_closed(wb)
return True
class WebBuffer(QWebEnginePage):
"""
Represent some web page content.
"""
LOGGER = logging.getLogger("webcontent")
JSMessageLevel = QWebEnginePage.JavaScriptConsoleMessageLevel
JSLEVEL2LOGGING = {
JSMessageLevel.InfoMessageLevel: logging.INFO,
JSMessageLevel.WarningMessageLevel: logging.WARNING,
JSMessageLevel.ErrorMessageLevel: logging.ERROR,
}
def __init__(self, url=None):
"""
Create a webbuffer.
:param url: the url to use for the buffer. Must be an instance of QUrl,
an str or None to not load any url.
"""
QWebEnginePage.__init__(self, app().profile.q_profile, None)
self.last_use = time.time()
cb = current_buffer()
if cb:
BUFFERS.insert(BUFFERS.index(cb) + 1, self)
else:
BUFFERS.append(self)
hooks.webbuffer_created(self)
self.fullScreenRequested.connect(self._on_full_screen_requested)
self.featurePermissionRequested.connect(self._on_feature_requested)
self._content_handler = WebContentHandler(self)
channel = QWebChannel(self)
channel.registerObject("contentHandler", self._content_handler)
self.setWebChannel(channel,
QWebEngineScript.ScriptWorldId.ApplicationWorld)
self.loadFinished.connect(self.finished)
self.authenticationRequired.connect(self.handle_authentication)
self.linkHovered.connect(self.on_url_hovered)
self.titleChanged.connect(self.__on_title_changed)
self.__delay_loading_url = None
self.__keymap_mode = Mode.KEYMAP_NORMAL
self.__mode = get_mode("standard-mode")
self.__text_edit_mark = False
self._internal_view = None
if url:
if isinstance(url, DelayedLoadingUrl):
self.__delay_loading_url = url
else:
self.load(url)
def internal_view(self):
return self._internal_view
def view(self):
iv = self._internal_view
if iv and iv.isVisible():
return iv.view()
@property
def mode(self):
return self.__mode
@property
def text_edit_mark(self):
return self.__text_edit_mark
def set_text_edit_mark(self, on):
self.__text_edit_mark = on
def set_mode(self, modename):
if self.__mode.name == modename:
return
old_mode = self.__mode
self.__mode = get_mode(modename)
LOCAL_KEYMAP_SETTER.buffer_mode_changed(self, old_mode)
def load(self, url):
if not isinstance(url, QUrl):
url = QUrl.fromUserInput(url)
self.__delay_loading_url = None
return QWebEnginePage.load(self, url)
def delayed_loading_url(self):
return self.__delay_loading_url
def url(self):
if self.__delay_loading_url:
return self.__delay_loading_url.url
return QWebEnginePage.url(self)
def title(self):
if self.__delay_loading_url:
return self.__delay_loading_url.title
return QWebEnginePage.title(self)
@property
def content_handler(self):
return self._content_handler
def javaScriptConsoleMessage(self, level, message, lineno, source):
logger = self.LOGGER
# small speed improvement, avoid to log if unnecessary
if logger.level < logging.CRITICAL:
level = self.JSLEVEL2LOGGING.get(level, logging.ERROR)
logger.log(level, message, extra={"url": self.url().toString()})
def active_keymap(self):
return self.mode.keymap_for_mode(self.__keymap_mode)
@property
def keymap_mode(self):
return self.__keymap_mode
def set_keymap_mode(self, enabled):
self.__keymap_mode = enabled
def async_scroll_pos(self, func):
self.runJavaScript("[window.pageXOffset, window.pageYOffset]", func)
def set_scroll_pos(self, x=0, y=0):
self.runJavaScript("window.scrollTo(%d, %d);" % (x, y))
def scroll_by(self, x=0, y=0):
self.runJavaScript("window.scrollBy(%d, %d);" % (x, y))
def start_select_browser_objects(self, selector, method="filter",
method_options=None):
self.runJavaScript(
"hints.selectBrowserObjects(%r, %r, %r);"
% (selector, method, json.dumps(method_options)),
QWebEngineScript.ScriptWorldId.ApplicationWorld)
def stop_select_browser_objects(self):
self.runJavaScript(
"hints.clearBrowserObjects();",
QWebEngineScript.ScriptWorldId.ApplicationWorld)
def select_nex_browser_object(self, forward=True):
self.runJavaScript(
"hints.activateNextHint(%s);" % ("false" if forward else "true",),
QWebEngineScript.ScriptWorldId.ApplicationWorld)
def filter_browser_objects(self, text):
self.runJavaScript(
"hints.filterSelection(%r);" % text,
QWebEngineScript.ScriptWorldId.ApplicationWorld)
def focus_active_browser_object(self):
self.runJavaScript(
"hints.followCurrentLink();",
QWebEngineScript.ScriptWorldId.ApplicationWorld)
def select_visible_hint(self, hint_id):
self.runJavaScript(
"hints.selectVisibleHint(%r);" % hint_id,
QWebEngineScript.ScriptWorldId.ApplicationWorld)
@Slot("QWebEngineFullScreenRequest")
def _on_full_screen_requested(self, request):
internal_view = self.internal_view()
if not internal_view:
return
if internal_view.request_fullscreen(request.toggleOn()):
request.accept()
@Slot(QUrl, QWebEnginePage.Feature)
def _on_feature_requested(self, url, feature):
permission_db = app().features()
permission = permission_db.get_permission(url.host(), feature)
if permission == QWebEnginePage.PermissionPolicy.PermissionUnknown:
prompt = YesNoPrompt("Allow enabling feature {} for {}?"
.format(feature.name, url.toString()),
always=True,
never=True)
answer = current_minibuffer().do_prompt(prompt, flash=True)
if answer in (YesNoPrompt.YES, YesNoPrompt.ALWAYS):
permission = QWebEnginePage.PermissionPolicy.PermissionGrantedByUser
elif answer in (YesNoPrompt.NO, YesNoPrompt.NEVER):
permission = QWebEnginePage.PermissionPolicy.PermissionDeniedByUser
else:
permission = QWebEnginePage.PermissionPolicy.PermissionUnknown
if answer in (YesNoPrompt.ALWAYS, YesNoPrompt.NEVER):
permission_db.set_permission(url.host(), feature, permission)
self.setFeaturePermission(url, feature, permission)
def createWindow(self, type):
buffer = create_buffer()
view = self.view()
if view is None:
view = current_window().current_webview()
def open_in_view():
view.setBuffer(buffer)
call_later(open_in_view)
return buffer
def finished(self):
url = self.url()
if url.isValid() and not url.scheme() == "webmacs":
app().visitedlinks().visit(url.toString(), self.title())
self.set_mode(get_auto_modename_for_url(self.url().toString()))
hooks.webbuffer_load_finished(self)
# We lose the keyboard focus without that with Qt 5.11. Though it
# happens quite randomly, but a combination of follow, go back, google
# something and the issue happens. I was not seeing this with Qt5.9.
view = self.view()
if view and not LOCAL_KEYMAP_SETTER.enabled_minibuffer \
and view.main_window.current_webview() == view:
view.internal_view().setFocus()
def handle_authentication(self, url, authenticator):
password_manager = app().profile.password_manager
try:
credential = password_manager.credential_for_url(url.toString())
except PasswordManagerNotReady:
credential = None
if credential:
authenticator.setUser(credential.username)
authenticator.setPassword(credential.password)
return
# ask authentication credentials
prompt = AskPasswordPrompt(self)
current_minibuffer().do_prompt(prompt, flash=True)
authenticator.setUser(prompt.username)
authenticator.setPassword(prompt.password)
def certificateError(self, error):
url = "{}:{}".format(error.url().host(), error.url().port(80))
db = app().ignored_certs()
if db.is_ignored(url):
return True
prompt = YesNoPrompt("[certificate error] {} - ignore ? "
.format(error.errorDescription()), always=True)
current_minibuffer().do_prompt(prompt, flash=True)
if prompt.value() == YesNoPrompt.ALWAYS:
db.ignore(url)
return prompt.value() in (YesNoPrompt.ALWAYS, YesNoPrompt.YES)
def javaScriptConfirm(self, url, msg):
return current_minibuffer().do_prompt(
YesNoPrompt("[js-confirm] {} ".format(msg)),
flash=True,
)
def javaScriptAlert(self, url, msg):
msg = "[js-alert] {}".format(msg)
minibuffer_show_info(msg)
def on_url_hovered(self, url):
minibuffer_show_info(url)
def main_window(self):
view = self.view()
if view:
return view.main_window
def _incr_zoom(self, forward):
# Zooming constants
ZOOM_MIN = 25
ZOOM_MAX = 500
ZOOM_INC = 25
zoom = self.zoomFactor()*100
# We need to round up because the zoom factor is stored as a float
self.set_zoom(round(min(ZOOM_MAX, max(ZOOM_MIN, zoom +
(ZOOM_INC if forward
else -ZOOM_INC)))))
def set_zoom(self, zoom_factor):
if zoom_factor is not None:
self.setZoomFactor(zoom_factor/100)
minibuffer_show_info("Zoom level: %d%%" % (zoom_factor))
def zoom_in(self):
self._incr_zoom(True)
def zoom_out(self):
self._incr_zoom(False)
def zoom_normal(self):
self.set_zoom(100)
@Slot(str)
def __on_title_changed(self, title):
if self.view():
self.view().main_window.update_title(title)
# alias to create a web buffer
create_buffer = WebBuffer
================================================
FILE: webmacs/webview.py
================================================
# This file is part of webmacs.
#
# webmacs is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# webmacs is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with webmacs. If not, see .
import time
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import QFrame, QVBoxLayout, QWidget
from PyQt6.QtCore import QEvent
from .keyboardhandler import local_keymap, set_local_keymap, \
LOCAL_KEYMAP_SETTER
from . import windows, variables
def _update_stylesheets(var):
for w in windows():
for view in w.webviews():
view.setStyleSheet(var.value)
webview_stylesheet = variables.define_variable(
"webview-stylesheet",
"stylesheet associated to the webviews.",
"""\
[single=false][current=true] {
border-top: 1px solid black;
padding: 1px;
background-color: red;
}
[single=false][current=false] {
border-top: 1px solid white;
padding: 1px;
}\
""",
callbacks=(_update_stylesheets,),
type=variables.String(),
)
class WebView(QFrame):
def __init__(self, window):
QFrame.__init__(self)
self.main_window = window
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
self.setLayout(layout)
self.setStyleSheet(webview_stylesheet.value)
def _attach_view(self, view):
if self.layout().count() == 0:
self.layout().addWidget(view)
def _detach_view(self):
if self.layout().count() > 0:
it = self.layout().takeAt(0)
it.widget().setParent(None)
def setBuffer(self, buffer, update_last_use=True):
otherviews = [w for w in self.main_window.webviews()
if w != self]
for v in otherviews:
# this prevent multi views from being scrolled to the
# right; to reproduce, C-x 3, C-x o, then C-x f and open
# something
iv = v.internal_view()
if iv:
iv.setFocus()
self._detach_view()
if buffer is None:
self.main_window.update_title()
return
if buffer._internal_view is None:
buffer._internal_view = InternalWebView(self)
buffer._internal_view.setPage(buffer)
else:
buffer._internal_view._view = self
self._attach_view(buffer._internal_view)
url = buffer.delayed_loading_url()
if url:
buffer.load(url.url)
self.main_window.update_title()
LOCAL_KEYMAP_SETTER.buffer_opened_in_view(buffer)
# mark the buffer to be the most recently opened
if update_last_use:
buffer.last_use = time.time()
if self.main_window.current_webview() == self:
# keyboard focus is lost without that.
buffer._internal_view.setFocus()
self.show_focused(True)
def buffer(self):
if self.internal_view():
return self.internal_view().page()
def show_focused(self, active):
self.setProperty("current", active)
self.setProperty("single",
len(self.main_window.webviews()) == 1)
# force the style to be taken into account
self.setStyle(self.style())
def internal_view(self):
if self.layout().count() > 0:
return self.layout().itemAt(0).widget()
class InternalWebView(QWebEngineView):
"""Do not instantiate that class directly"""
def __init__(self, view):
QWebEngineView.__init__(self)
self._view = view
self._fullscreen_state = None
def view(self):
return self._view
def event(self, evt):
if evt.type() == QEvent.Type.ChildAdded:
obj = evt.child()
if isinstance(obj, QWidget):
obj.installEventFilter(self)
return QWebEngineView.event(self, evt)
def eventFilter(self, obj, evt):
view = self.view()
t = evt.type()
if t == QEvent.Type.MouseButtonPress:
if view != view.main_window.current_webview():
view.main_window.set_current_webview(view)
elif t == QEvent.Type.FocusIn:
if self.isEnabled(): # disabled when there is a full-screen window
LOCAL_KEYMAP_SETTER.view_focus_changed(view, True)
elif t == QEvent.Type.FocusOut:
if self.isEnabled(): # disabled when there is a full-screen window
LOCAL_KEYMAP_SETTER.view_focus_changed(view, False)
return False
def request_fullscreen(self, toggle_on):
if toggle_on:
if self._fullscreen_state:
return
self._fullscreen_state = FullScreenState(self)
return True
else:
if not self._fullscreen_state:
return
self._fullscreen_state.restore()
self._fullscreen_state = None
return True
class FullScreenState(object):
def __init__(self, internal_view):
self.view = internal_view.view()
self.internal_view = internal_view
self.keymap = local_keymap()
set_local_keymap(self.view.buffer().mode.fullscreen_keymap())
self.view._detach_view()
# show fullscreen on the right place.
screen = self.view.screen()
self.internal_view.showFullScreen()
self.internal_view.setGeometry(screen.geometry())
self.view.main_window.fullscreen_window = self
def restore(self):
set_local_keymap(self.keymap)
self.internal_view.showNormal()
self.view._attach_view(self.internal_view)
self.view.main_window.fullscreen_window = None
================================================
FILE: webmacs/window.py
================================================
# This file is part of webmacs.
#
# webmacs is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# webmacs is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with webmacs. If not, see .
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QToolBar
from PyQt6.QtCore import Qt, QRect
from .minibuffer import Minibuffer
from .egrid import ViewGridLayout
from . import WINDOWS_HANDLER
from . import hooks, variables
window_toolbar_on_startup = variables.define_variable(
"window-toolbar-on-startup",
"If set to True, the main window(s) will have the navigation toolbar"
" visible on startup.",
False,
type=variables.Bool(),
)
def remove_layout_spaces(layout):
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
self._layout = QVBoxLayout()
remove_layout_spaces(self._layout)
self.setLayout(self._layout)
self._central_widget = QWidget()
self._layout.addWidget(self._central_widget)
self._webviews_layout = ViewGridLayout(self)
remove_layout_spaces(self._webviews_layout)
self._central_widget.setLayout(self._webviews_layout)
self._minibuffer = Minibuffer(self)
self._layout.addWidget(self._minibuffer)
self.fullscreen_window = None
self.quit_if_last_closed = True
WINDOWS_HANDLER.register_window(self)
self._toolbar = None
if window_toolbar_on_startup.value:
self.toggle_toolbar()
# remove the toolbar update callback if it was set
self.destroyed.connect(lambda:
hooks.webbuffer_current_changed
.remove_if_exists(self._update_toolbar))
def _update_toolbar(self, buffer):
if buffer.view().main_window != self:
return
self._toolbar.clear()
self._toolbar.addAction(buffer.action(buffer.WebAction.Back))
self._toolbar.addAction(buffer.action(buffer.WebAction.Forward))
self._toolbar.addSeparator()
self._toolbar.addAction(buffer.action(buffer.WebAction.Stop))
self._toolbar.addAction(buffer.action(buffer.WebAction.Reload))
def toggle_toolbar(self):
if self._toolbar is None:
hooks.webbuffer_current_changed.add(self._update_toolbar)
self._toolbar = QToolBar()
self._layout.insertWidget(0, self._toolbar)
current_view = self.current_webview()
if current_view and current_view.buffer():
self._update_toolbar(current_view.buffer())
else:
hooks.webbuffer_current_changed.remove(self._update_toolbar)
self._layout.removeWidget(self._toolbar)
self._toolbar.deleteLater()
self._toolbar = None
def set_current_webview(self, webview):
self.current_webview().show_focused(False)
if len(self.webviews()) > 1:
webview.show_focused(True)
self._webviews_layout.set_current_view(webview)
def current_webview(self):
return self._webviews_layout.current_view()
def webviews(self):
return self._webviews_layout.views()
def create_webview_on_right(self):
return self._webviews_layout.split_view(ViewGridLayout.VERTICAL)
def create_webview_on_bottom(self):
return self._webviews_layout.split_view(ViewGridLayout.HORIZONTAL)
def _delete_webview(self, webview):
webview.setBuffer(None)
self._webviews_layout.removeWidget(webview)
webview.deleteLater()
def minibuffer(self):
return self._minibuffer
def other_view(self):
"""switch to the next view"""
views = self.webviews()
index = views.index(self.current_webview())
index = index + 1
if index >= len(views):
index = 0
self.set_current_webview(views[index])
def close_view(self, view):
"""close the given view"""
views = self.webviews()
if len(views) == 1:
return # can't delete a single view
if view == self.current_webview():
self.other_view()
self._delete_webview(view)
# do not show the window focused if there is one left
if len(self.webviews()) == 1:
self.current_webview().show_focused(True)
def close_other_views(self):
"""close all views but the current one"""
view = self.current_webview()
# to remove more than one item correctly, the iteration must
# be done on a shallow copy of the list
for other in list(self.webviews()):
if view != other:
self._delete_webview(other)
# do not show the window focused if there is one left
if len(self.webviews()) == 1:
self.current_webview().show_focused(False)
def update_title(self, title=None):
if title is None:
mw = self.current_webview()
if mw and mw.buffer():
title = mw.buffer().title()
if title:
self.setWindowTitle("{} - Webmacs".format(title))
else:
self.setWindowTitle("Webmacs")
def dump_state(self):
return {
"geometry": self.geometry().getRect(),
"window-state": self.windowState().value,
"view-layout": self._webviews_layout.dump_state(),
}
def restore_state(self, data, version):
self.setGeometry(QRect(*data["geometry"]))
for e in Qt.WindowState:
if e.value == data["window-state"]:
self.setWindowState(e)
break
self._webviews_layout.restore_state(data["view-layout"])