From 4727c13c41c3660ebf5621bfa4c99042cccdb49b Mon Sep 17 00:00:00 2001 From: Tom Schuster Date: Sat, 2 Jun 2012 20:15:38 +0200 Subject: [PATCH 01/14] Bug 752226 - Remove JSVAL_IS_OBJECT from the jsapi. r=dmandelin --HG-- extra : rebase_source : 0943efd64ddf199a4cef12072239302408e5aae5 --- .../html/content/src/nsHTMLMediaElement.cpp | 2 +- dom/system/OSFileConstants.cpp | 21 ++++++++++--------- js/src/jsapi.h | 8 +------ 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/content/html/content/src/nsHTMLMediaElement.cpp b/content/html/content/src/nsHTMLMediaElement.cpp index ae36dba18b22..b71b3839983f 100644 --- a/content/html/content/src/nsHTMLMediaElement.cpp +++ b/content/html/content/src/nsHTMLMediaElement.cpp @@ -454,7 +454,7 @@ nsHTMLMediaElement::GetSrc(JSContext* aCtx, jsval *aParams) NS_IMETHODIMP nsHTMLMediaElement::SetSrc(JSContext* aCtx, const jsval & aParams) { - if (aParams.isObject()) { // not JSVAL_IS_OBJECT() because a null object is still an object + if (aParams.isObject()) { nsCOMPtr stream; stream = do_QueryInterface(nsContentUtils::XPConnect()-> GetNativeOfWrapper(aCtx, JSVAL_TO_OBJECT(aParams))); diff --git a/dom/system/OSFileConstants.cpp b/dom/system/OSFileConstants.cpp index a5ac1dee27b4..af0f7809782f 100644 --- a/dom/system/OSFileConstants.cpp +++ b/dom/system/OSFileConstants.cpp @@ -258,17 +258,18 @@ static dom::ConstantSpec gWinProperties[] = JSObject *GetOrCreateObjectProperty(JSContext *cx, JSObject *aObject, const char *aProperty) { - jsval val; - if (JS_GetProperty(cx, aObject, aProperty, &val) - && !JSVAL_IS_VOID(val)) { - if (JSVAL_IS_OBJECT(val)) { - return JSVAL_TO_OBJECT(val); - } - else { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, - JSMSG_UNEXPECTED_TYPE, aProperty, "not an object"); - return NULL; + JS::Value val; + if (!JS_GetProperty(cx, aObject, aProperty, &val)) { + return NULL; + } + if (!val.isUndefined()) { + if (val.isObject()) { + return &val.toObject(); } + + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_UNEXPECTED_TYPE, aProperty, "not an object"); + return NULL; } return JS_DefineObject(cx, aObject, aProperty, NULL, NULL, JSPROP_ENUMERATE); } diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 5e83f235aa06..f27df2712ee6 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1870,16 +1870,10 @@ STRING_TO_JSVAL(JSString *str) return IMPL_TO_JSVAL(STRING_TO_JSVAL_IMPL(str)); } -static JS_ALWAYS_INLINE JSBool -JSVAL_IS_OBJECT(jsval v) -{ - return JSVAL_IS_OBJECT_OR_NULL_IMPL(JSVAL_TO_IMPL(v)); -} - static JS_ALWAYS_INLINE JSObject * JSVAL_TO_OBJECT(jsval v) { - JS_ASSERT(JSVAL_IS_OBJECT(v)); + JS_ASSERT(JSVAL_IS_OBJECT_OR_NULL_IMPL(JSVAL_TO_IMPL(v))); return JSVAL_TO_OBJECT_IMPL(JSVAL_TO_IMPL(v)); } From 59432c848c63312028a0526d4be9e46e6eadfe48 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Sat, 2 Jun 2012 20:16:24 +0200 Subject: [PATCH 02/14] Bug 757676: implement default parameters in JaegerMonkey, r=bhackett --HG-- extra : rebase_source : f5220941d9502d49b0c230081c265f845396a251 --- js/src/jsanalyze.cpp | 1 + js/src/methodjit/Compiler.cpp | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/js/src/jsanalyze.cpp b/js/src/jsanalyze.cpp index 20a910ce80af..95eb78d2f486 100644 --- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -589,6 +589,7 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx) case JSOP_HOLE: case JSOP_LOOPHEAD: case JSOP_LOOPENTRY: + case JSOP_ACTUALSFILLED: break; default: diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 6b0b38c8586f..375cbaddefca 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -2253,6 +2253,25 @@ mjit::Compiler::generateMethod() frame.push(MagicValue(JS_OPTIMIZED_ARGUMENTS)); } END_CASE(JSOP_ARGUMENTS) + BEGIN_CASE(JSOP_ACTUALSFILLED) + { + + // We never inline things with defaults because of the switch. + JS_ASSERT(a == outer); + RegisterID value = frame.allocReg(), nactual = frame.allocReg(); + int32_t defstart = GET_UINT16(PC); + masm.move(Imm32(defstart), value); + masm.load32(Address(JSFrameReg, StackFrame::offsetOfNumActual()), nactual); + + // Best would be a single instruction where available (like + // cmovge on x86), but there's no way get that yet, so jump. + Jump j = masm.branch32(Assembler::LessThan, nactual, Imm32(defstart)); + masm.move(nactual, value); + j.linkTo(masm.label(), &masm); + frame.freeReg(nactual); + frame.pushInt32(value); + } + END_CASE(JSOP_ACTUALSFILLED) BEGIN_CASE(JSOP_ITERNEXT) iterNext(GET_INT8(PC)); From 6d98894d864123dbebd3cc2222ceefd8deedec6b Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Sat, 2 Jun 2012 20:16:24 +0200 Subject: [PATCH 03/14] Bug 759498: fix defaults when arguments are bound as functions, r=jorendorff --HG-- extra : rebase_source : 6671517779bbaaf7cf1fd6410255823f877345ad --- js/src/frontend/BytecodeCompiler.cpp | 7 + js/src/frontend/BytecodeEmitter.cpp | 154 ++++++++++-------- js/src/frontend/ParseNode.h | 4 +- js/src/frontend/Parser.cpp | 41 +++-- js/src/frontend/Parser.h | 2 +- .../arguments/defaults-bound-to-function.js | 22 +++ .../tests/arguments/defaults-decompile.js | 10 ++ js/src/jsopcode.cpp | 82 ++++++---- 8 files changed, 196 insertions(+), 126 deletions(-) create mode 100644 js/src/jit-test/tests/arguments/defaults-bound-to-function.js diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index e48acac55d36..b102af33d5be 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -289,6 +289,13 @@ frontend::CompileFunctionBody(JSContext *cx, JSFunction *fun, fn->pn_body = NULL; fn->pn_cookie.makeFree(); + ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, &parser); + if (!argsbody) + return false; + argsbody->setOp(JSOP_NOP); + argsbody->makeEmpty(); + fn->pn_body = argsbody; + unsigned nargs = fun->nargs; if (nargs) { /* diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index bc5b39952934..bf4b1027ba26 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -5103,52 +5103,8 @@ EmitStatementList(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t ParseNode *pnchild = pn->pn_head; - // Destructuring is handled in args body for functions with default - // arguments. - if (pn->pn_xflags & PNX_DESTRUCT && bce->sc->fun()->hasDefaults()) + if (pn->pn_xflags & PNX_DESTRUCT) pnchild = pnchild->pn_next; - if (pn->pn_xflags & PNX_FUNCDEFS) { - /* - * This block contains top-level function definitions. To ensure - * that we emit the bytecode defining them before the rest of code - * in the block we use a separate pass over functions. During the - * main pass later the emitter will add JSOP_NOP with source notes - * for the function to preserve the original functions position - * when decompiling. - * - * Currently this is used only for functions, as compile-as-we go - * mode for scripts does not allow separate emitter passes. - */ - JS_ASSERT(bce->sc->inFunction); - if (pn->pn_xflags & PNX_DESTRUCT && !bce->sc->fun()->hasDefaults()) { - /* - * Assign the destructuring arguments before defining any - * functions, see bug 419662. - */ - JS_ASSERT(pnchild->isKind(PNK_SEMI)); - JS_ASSERT(pnchild->pn_kid->isKind(PNK_VAR) || pnchild->pn_kid->isKind(PNK_CONST)); - if (!EmitTree(cx, bce, pnchild)) - return false; - pnchild = pnchild->pn_next; - } - - for (ParseNode *pn2 = pnchild; pn2; pn2 = pn2->pn_next) { - if (pn2->isKind(PNK_FUNCTION)) { - if (pn2->isOp(JSOP_NOP)) { - if (!EmitTree(cx, bce, pn2)) - return false; - } else { - /* - * JSOP_DEFFUN in a top-level block with function - * definitions appears, for example, when "if (true)" - * is optimized away from "if (true) function x() {}". - * See bug 428424. - */ - JS_ASSERT(pn2->isOp(JSOP_DEFFUN)); - } - } - } - } for (ParseNode *pn2 = pnchild; pn2; pn2 = pn2->pn_next) { if (!EmitTree(cx, bce, pn2)) @@ -5869,12 +5825,7 @@ static bool EmitDefaults(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) { JS_ASSERT(pn->isKind(PNK_ARGSBODY)); - ParseNode *pnlast = pn->last(); - unsigned ndefaults = 0; - for (ParseNode *arg = pn->pn_head; arg != pnlast; arg = arg->pn_next) { - if (arg->pn_expr) - ndefaults++; - } + uint16_t ndefaults = bce->sc->funbox->ndefaults; JSFunction *fun = bce->sc->fun(); unsigned nformal = fun->nargs - fun->hasRest(); EMIT_UINT16_IMM_OP(JSOP_ACTUALSFILLED, nformal - ndefaults); @@ -5884,23 +5835,55 @@ EmitDefaults(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) return false; jsbytecode *pc = bce->code(top + JUMP_OFFSET_LEN); JS_ASSERT(nformal >= ndefaults); - SET_JUMP_OFFSET(pc, nformal - ndefaults); + uint16_t defstart = nformal - ndefaults; + SET_JUMP_OFFSET(pc, defstart); pc += JUMP_OFFSET_LEN; SET_JUMP_OFFSET(pc, nformal - 1); pc += JUMP_OFFSET_LEN; // Fill body of switch, which sets defaults where needed. - for (ParseNode *arg = pn->pn_head; arg != pnlast; arg = arg->pn_next) { - if (!arg->pn_expr) + unsigned i; + ParseNode *arg, *pnlast = pn->last(); + for (arg = pn->pn_head, i = 0; arg != pnlast; arg = arg->pn_next, i++) { + if (!(arg->pn_dflags & PND_DEFAULT)) continue; SET_JUMP_OFFSET(pc, bce->offset() - top); pc += JUMP_OFFSET_LEN; - if (!EmitTree(cx, bce, arg->pn_expr)) - return false; - if (!BindNameToSlot(cx, bce, arg)) - return false; - if (!EmitVarOp(cx, arg, JSOP_SETARG, bce)) + ParseNode *expr; + if (arg->isKind(PNK_NAME)) { + expr = arg->expr(); + } else { + // The argument name is bound to a function. We still have to + // evaluate the default in case it has side effects. + JS_ASSERT(!arg->isDefn()); + JS_ASSERT(arg->isKind(PNK_ASSIGN)); + expr = arg->pn_right; + } + if (!EmitTree(cx, bce, expr)) return false; + if (arg->isKind(PNK_NAME)) { + if (!BindNameToSlot(cx, bce, arg)) + return false; + if (!EmitVarOp(cx, arg, JSOP_SETARG, bce)) + return false; + } else { + // Create a dummy JSOP_SETLOCAL for the decompiler. Jump over it + // with a JSOP_GOTO in real code. + if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0) + return false; + ptrdiff_t hop = bce->offset(); + if (EmitJump(cx, bce, JSOP_GOTO, 0) < 0) + return false; + + unsigned slot; + bce->sc->bindings.lookup(cx, arg->pn_left->atom(), &slot); + + // It doesn't matter if this is correct with respect to aliasing or + // not. Only the decompiler is going to see it. + if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, slot, bce)) + return false; + SET_JUMP_OFFSET(bce->code(hop), bce->offset() - hop); + } if (Emit1(cx, bce, JSOP_POP) < 0) return false; } @@ -5932,22 +5915,52 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) { JSFunction *fun = bce->sc->fun(); ParseNode *pnlast = pn->last(); - if (fun->hasDefaults()) { - if (pnlast->pn_xflags & PNX_DESTRUCT) { - JS_ASSERT(pnlast->pn_head->isKind(PNK_SEMI)); - // Defaults must be able to access destructured arguments, so do - // that now. - if (!EmitTree(cx, bce, pnlast->pn_head)) - return false; + // Carefully emit everything in the right order: + // 1. Destructuring + // 2. Functions + // 3. Defaults + ParseNode *pnchild = pnlast->pn_head; + if (pnlast->pn_xflags & PNX_DESTRUCT) { + // Assign the destructuring arguments before defining any functions, + // see bug 419662. + JS_ASSERT(pnchild->isKind(PNK_SEMI)); + JS_ASSERT(pnchild->pn_kid->isKind(PNK_VAR) || pnchild->pn_kid->isKind(PNK_CONST)); + if (!EmitTree(cx, bce, pnchild)) + return false; + pnchild = pnchild->pn_next; + } + if (pnlast->pn_xflags & PNX_FUNCDEFS) { + // This block contains top-level function definitions. To ensure + // that we emit the bytecode defining them before the rest of code + // in the block we use a separate pass over functions. During the + // main pass later the emitter will add JSOP_NOP with source notes + // for the function to preserve the original functions position + // when decompiling. + + // Currently this is used only for functions, as compile-as-we go + // mode for scripts does not allow separate emitter passes. + for (ParseNode *pn2 = pnchild; pn2; pn2 = pn2->pn_next) { + if (pn2->isKind(PNK_FUNCTION)) { + if (pn2->isOp(JSOP_NOP)) { + if (!EmitTree(cx, bce, pn2)) + return false; + } else { + // JSOP_DEFFUN in a top-level block with function + // definitions appears, for example, when "if (true)" + // is optimized away from "if (true) function x() {}". + // See bug 428424. + JS_ASSERT(pn2->isOp(JSOP_DEFFUN)); + } + } } - + } + if (fun->hasDefaults()) { ParseNode *rest = NULL; if (fun->hasRest()) { JS_ASSERT(!bce->sc->funArgumentsHasLocalBinding()); - - // Defaults and rest also need special handling. The rest - // parameter needs to be undefined while defaults are being + // Defaults with a rest parameter need special handling. The + // rest parameter needs to be undefined while defaults are being // processed. To do this, we create the rest argument and let it // sit on the stack while processing defaults. The rest // parameter's slot is set to undefined for the course of @@ -5984,7 +5997,6 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) return JS_FALSE; } if (pn2->pn_next == pnlast && fun->hasRest() && !fun->hasDefaults()) { - // Fill rest parameter. We handled the case with defaults above. JS_ASSERT(!bce->sc->funArgumentsHasLocalBinding()); bce->switchToProlog(); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index b5fc6148e549..e48010d1f0d3 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -738,6 +738,7 @@ struct ParseNode { still valid, but this use no longer optimizable via an upvar opcode */ #define PND_CLOSED 0x200 /* variable is closed over */ +#define PND_DEFAULT 0x400 /* definition is an arg with a default */ /* Flags to propagate from uses to definition. */ #define PND_USE2DEF_FLAGS (PND_ASSIGNED | PND_CLOSED) @@ -1406,7 +1407,7 @@ struct Definition : public ParseNode enum Kind { VAR, CONST, LET, FUNCTION, ARG, UNKNOWN }; - bool isBindingForm() { return int(kind()) <= int(LET); } + bool canHaveInitializer() { return int(kind()) <= int(LET) || kind() == ARG; } static const char *kindString(Kind kind); @@ -1510,6 +1511,7 @@ struct FunctionBox : public ObjectBox FunctionBox *parent; Bindings bindings; /* bindings for this function */ uint32_t level:JSFB_LEVEL_BITS; + uint16_t ndefaults; bool queued:1; bool inLoop:1; /* in a loop in parent function */ bool inWith:1; /* some enclosing scope is a with-statement diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 90e744847420..732a8088e98e 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -178,6 +178,7 @@ FunctionBox::FunctionBox(ObjectBox* traceListHead, JSObject *obj, ParseNode *fn, parent(tc->sc->funbox), bindings(tc->sc->context), level(tc->sc->staticLevel), + ndefaults(0), queued(false), inLoop(false), inWith(!!tc->innermostWith), @@ -870,7 +871,7 @@ MakeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom, Parser *parser) * must rewrite it to be an assignment node, whose freshly allocated * left-hand side becomes a use of pn. */ - if (dn->isBindingForm()) { + if (dn->canHaveInitializer()) { ParseNode *rhs = dn->expr(); if (rhs) { ParseNode *lhs = MakeAssignment(dn, rhs, parser); @@ -930,14 +931,6 @@ js::DefineArg(ParseNode *pn, JSAtom *atom, unsigned i, Parser *parser) return false; ParseNode *argsbody = pn->pn_body; - if (!argsbody) { - argsbody = ListNode::create(PNK_ARGSBODY, parser); - if (!argsbody) - return false; - argsbody->setOp(JSOP_NOP); - argsbody->makeEmpty(); - pn->pn_body = argsbody; - } argsbody->append(argpn); argpn->setOp(JSOP_GETARG); @@ -1286,17 +1279,24 @@ LeaveFunction(ParseNode *fn, Parser *parser, PropertyName *funName = NULL, } bool -Parser::functionArguments(ParseNode **listp, bool &hasDefaults, bool &hasRest) +Parser::functionArguments(ParseNode **listp, bool &hasRest) { if (tokenStream.getToken() != TOK_LP) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_PAREN_BEFORE_FORMAL); return false; } - hasDefaults = false; hasRest = false; + ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, this); + if (!argsbody) + return false; + argsbody->setOp(JSOP_NOP); + argsbody->makeEmpty(); + tc->sc->funbox->node->pn_body = argsbody; + if (!tokenStream.matchToken(TOK_RP)) { + bool hasDefaults = false; #if JS_HAS_DESTRUCTURING JSAtom *duplicatedArg = NULL; bool destructuringArg = false; @@ -1430,7 +1430,10 @@ Parser::functionArguments(ParseNode **listp, bool &hasDefaults, bool &hasRest) ParseNode *def_expr = assignExprWithoutYield(JSMSG_YIELD_IN_DEFAULT); if (!def_expr) return false; - tc->sc->funbox->node->pn_body->last()->pn_expr = def_expr; + ParseNode *arg = tc->sc->funbox->node->pn_body->last(); + arg->pn_dflags |= PND_DEFAULT; + arg->pn_expr = def_expr; + tc->sc->funbox->ndefaults++; } else if (!hasRest && hasDefaults) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT); return false; @@ -1595,12 +1598,12 @@ Parser::functionDef(HandlePropertyName funName, FunctionType type, FunctionSynta /* Now parse formal argument list and compute fun->nargs. */ ParseNode *prelude = NULL; - bool hasRest, hasDefaults; - if (!functionArguments(&prelude, hasDefaults, hasRest)) + bool hasRest; + if (!functionArguments(&prelude, hasRest)) return NULL; fun->setArgCount(funsc.bindings.numArgs()); - if (hasDefaults) + if (funbox->ndefaults) fun->setHasDefaults(); if (hasRest) fun->setHasRest(); @@ -1758,12 +1761,8 @@ Parser::functionDef(HandlePropertyName funName, FunctionType type, FunctionSynta pn->pn_funbox = funbox; pn->setOp(op); - if (pn->pn_body) { - pn->pn_body->append(body); - pn->pn_body->pn_pos = body->pn_pos; - } else { - pn->pn_body = body; - } + pn->pn_body->append(body); + pn->pn_body->pn_pos = body->pn_pos; JS_ASSERT_IF(!outertc->sc->inFunction && bodyLevel && kind == Statement, pn->pn_cookie.isFree()); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index df27f3f5a85f..d6e0c7b1de7f 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -210,7 +210,7 @@ struct Parser : private AutoGCRooter * Additional JS parsers. */ enum FunctionType { Getter, Setter, Normal }; - bool functionArguments(ParseNode **list, bool &hasDefaults, bool &hasRest); + bool functionArguments(ParseNode **list, bool &hasRest); ParseNode *functionDef(HandlePropertyName name, FunctionType type, FunctionSyntaxKind kind); diff --git a/js/src/jit-test/tests/arguments/defaults-bound-to-function.js b/js/src/jit-test/tests/arguments/defaults-bound-to-function.js new file mode 100644 index 000000000000..e8f25a6bbe01 --- /dev/null +++ b/js/src/jit-test/tests/arguments/defaults-bound-to-function.js @@ -0,0 +1,22 @@ +load(libdir + "asserts.js"); + +function f(a=42) { + return a; + function a() { return 19; } +} +assertEq(f()(), 19); +function h(a=b, b=43) { + return [a, b]; + function b() { return 42; } +} +var res = h(); +assertEq(res[0], res[1]); +assertEq(res[0](), 42); +assertThrowsInstanceOf(function i(b=FAIL) { + function b() {} +}, ReferenceError); +function j(a=(b=42), b=8) { + return b; + function b() {} +} +assertEq(j(), 42); diff --git a/js/src/jit-test/tests/arguments/defaults-decompile.js b/js/src/jit-test/tests/arguments/defaults-decompile.js index 9fed3a658233..f15b1648997e 100644 --- a/js/src/jit-test/tests/arguments/defaults-decompile.js +++ b/js/src/jit-test/tests/arguments/defaults-decompile.js @@ -32,3 +32,13 @@ function f11(a=(0, c=1)) {} assertEq(f11.length, 1); var g = eval("(" + f11 + ")"); assertEq(g.length, 1); +function f12(a, b=4) { + return b; + function b() { } +} +assertEq(f12.toString(), "function f12(a, b = 4) {\n return b;\n\n function b() {\n }\n\n}"); +function f13(a, b=4, ...rest) { + return b; + function b() { } +} +assertEq(f13.toString(), "function f13(a, b = 4, ...rest) {\n return b;\n\n function b() {\n }\n\n}"); diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index b1068e068268..2eeb14bb9c80 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -2543,6 +2543,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, int nb) JSString *str; JSBool ok; JSBool foreach; + JSBool defaultsSwitch = false; #if JS_HAS_XML_SUPPORT JSBool inXML, quoteAttr; #else @@ -4835,6 +4836,19 @@ Decompile(SprintStack *ss, jsbytecode *pc, int nb) int32_t j, n, low, high; TableEntry *table, *tmp; + if (defaultsSwitch) { + defaultsSwitch = false; + len = GET_JUMP_OFFSET(pc); + if (jp->fun->hasRest()) { + // Jump over rest parameter things. + len += GetBytecodeLength(pc + len); + LOCAL_ASSERT(pc[len] == JSOP_POP); + len += GetBytecodeLength(pc + len); + } + todo = -2; + break; + } + sn = js_GetSrcNote(jp->script, pc); LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH); len = js_GetSrcNoteOffset(sn, 0); @@ -5315,6 +5329,24 @@ Decompile(SprintStack *ss, jsbytecode *pc, int nb) break; #endif /* JS_HAS_XML_SUPPORT */ + case JSOP_ACTUALSFILLED: + JS_ASSERT(!defaultsSwitch); + defaultsSwitch = true; + todo = -2; + break; + + case JSOP_REST: + // Ignore bytecode related to handling rest. + pc += GetBytecodeLength(pc); + if (*pc == JSOP_UNDEFINED) + pc += GetBytecodeLength(pc); + LOCAL_ASSERT(*pc == JSOP_SETALIASEDVAR || *pc == JSOP_SETARG); + pc += GetBytecodeLength(pc); + LOCAL_ASSERT(*pc == JSOP_POP); + len = GetBytecodeLength(pc); + todo = -2; + break; + default: todo = -2; break; @@ -5539,6 +5571,24 @@ js_DecompileFunction(JSPrinter *jp) uint16_t defstart = 0; unsigned nformal = fun->nargs - fun->hasRest(); + if (fun->hasDefaults()) { + jsbytecode *defpc; + for (defpc = pc; defpc < endpc; defpc += GetBytecodeLength(defpc)) { + if (*defpc == JSOP_ACTUALSFILLED) + break; + } + LOCAL_ASSERT_RV(defpc < endpc, JS_FALSE); + defpc += GetBytecodeLength(defpc); + LOCAL_ASSERT_RV(*defpc == JSOP_TABLESWITCH, JS_FALSE); + defbegin = defpc; + deflen = GET_JUMP_OFFSET(defpc); + defpc += JUMP_OFFSET_LEN; + defstart = GET_JUMP_OFFSET(defpc); + defpc += JUMP_OFFSET_LEN; + defpc += JUMP_OFFSET_LEN; // Skip high + deftable = defpc; + } + for (unsigned i = 0; i < fun->nargs; i++) { if (i > 0) js_puts(jp, ", "); @@ -5555,7 +5605,6 @@ js_DecompileFunction(JSPrinter *jp) ptrdiff_t todo; const char *lval; - JS_ASSERT(deflen == 0); LOCAL_ASSERT(*pc == JSOP_GETARG || *pc == JSOP_GETALIASEDVAR); pc += js_CodeSpec[*pc].length; LOCAL_ASSERT(*pc == JSOP_DUP); @@ -5581,37 +5630,6 @@ js_DecompileFunction(JSPrinter *jp) } #endif - - // Compute default parameters. - if ((*pc == JSOP_REST && pc[1] == JSOP_UNDEFINED) || - *pc == JSOP_ACTUALSFILLED) { -#define SKIP(pc, op) LOCAL_ASSERT(*pc == op); pc += js_CodeSpec[op].length; - JS_ASSERT(fun->hasDefaults()); - JS_ASSERT(deflen == 0); - if (fun->hasRest()) { - SKIP(pc, JSOP_REST); - SKIP(pc, JSOP_UNDEFINED); - JS_ASSERT(*pc == JSOP_SETARG || *pc == JSOP_SETALIASEDVAR); - pc += js_CodeSpec[*pc].length; - SKIP(pc, JSOP_POP); - } - SKIP(pc, JSOP_ACTUALSFILLED); - JS_ASSERT(*pc == JSOP_TABLESWITCH); - defbegin = pc; - deflen = GET_JUMP_OFFSET(pc); - pc += JUMP_OFFSET_LEN; - defstart = GET_JUMP_OFFSET(pc); - pc += JUMP_OFFSET_LEN; - pc += JUMP_OFFSET_LEN; // Skip high - deftable = pc; - pc = defbegin + deflen; - if (fun->hasRest()) { - SKIP(pc, JSOP_SETARG); - SKIP(pc, JSOP_POP); - } -#undef SKIP - } - #undef LOCAL_ASSERT if (fun->hasDefaults() && deflen && i >= defstart && !isRest) { From e2c95974dc63a2c6252db80a9117ddbbe03f586b Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Sat, 2 Jun 2012 20:16:24 +0200 Subject: [PATCH 04/14] Bug 749818: add Number.isNaN, r=jwalden --HG-- extra : rebase_source : f3faa700eb75c6106b0ebb421d53080e293b714a --- js/src/jit-test/tests/basic/number-isnan.js | 15 +++++++++++++ js/src/jsnum.cpp | 24 +++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 js/src/jit-test/tests/basic/number-isnan.js diff --git a/js/src/jit-test/tests/basic/number-isnan.js b/js/src/jit-test/tests/basic/number-isnan.js new file mode 100644 index 000000000000..78e834591844 --- /dev/null +++ b/js/src/jit-test/tests/basic/number-isnan.js @@ -0,0 +1,15 @@ +assertEq(Number.isNaN(NaN), true); +assertEq(Number.isNaN(0/0), true); +assertEq(Number.isNaN(Number("NaN")), true); +assertEq(Number.isNaN(4), false); +assertEq(Number.isNaN(4.5), false); +assertEq(Number.isNaN("hi"), false); +assertEq(Number.isNaN("1.3"), false); +assertEq(Number.isNaN("51"), false); +assertEq(Number.isNaN(0), false); +assertEq(Number.isNaN(-0), false); +assertEq(Number.isNaN({valueOf: function () { return 3; }}), false); +assertEq(Number.isNaN({valueOf: function () { return 0/0; }}), false); +assertEq(Number.isNaN({ valueOf: function() { throw 17; } }), false); +assertEq(Number.isNaN({ toString: function() { throw 17; } }), false); +assertEq(Number.isNaN({ valueOf: function() { throw 17; }, toString: function() { throw 42; } }), false); diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 2d39362410db..e421201d3143 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -836,6 +836,27 @@ static JSFunctionSpec number_methods[] = { JS_FS_END }; + +// ES6 draft ES6 15.7.3.10 +static JSBool +Number_isNaN(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 1 || !args[0].isDouble()) { + args.rval().setBoolean(false); + return true; + } + args.rval().setBoolean(MOZ_DOUBLE_IS_NaN(args[0].toDouble())); + return true; +} + + +static JSFunctionSpec number_static_methods[] = { + JS_FN("isNaN", Number_isNaN, 1, 0), + JS_FS_END +}; + + /* NB: Keep this in synch with number_constants[]. */ enum nc_slot { NC_NaN, @@ -998,6 +1019,9 @@ js_InitNumberClass(JSContext *cx, JSObject *obj) if (!JS_DefineConstDoubles(cx, ctor, number_constants)) return NULL; + if (!DefinePropertiesAndBrand(cx, ctor, NULL, number_static_methods)) + return NULL; + if (!DefinePropertiesAndBrand(cx, numberProto, NULL, number_methods)) return NULL; From 9df5dec558cca2793db9855dc336de7638d45c31 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sat, 2 Jun 2012 11:23:16 -0700 Subject: [PATCH 05/14] Bug 744349 - Create message distribution mechanism for DBus Bluetooth Signals; r=cjones --- dom/bluetooth/BluetoothAdapter.cpp | 28 ++- dom/bluetooth/BluetoothAdapter.h | 12 + dom/bluetooth/BluetoothCommon.h | 48 +++- dom/bluetooth/BluetoothManager.cpp | 59 ++++- dom/bluetooth/BluetoothManager.h | 21 +- dom/bluetooth/BluetoothUtils.h | 77 ++++++ dom/bluetooth/Makefile.in | 26 +- dom/bluetooth/linux/BluetoothDBusUtils.cpp | 270 +++++++++++++++++++++ dom/system/gonk/SystemWorkerManager.cpp | 3 + ipc/dbus/DBusThread.cpp | 55 ++--- ipc/dbus/DBusThread.h | 22 +- ipc/dbus/DBusUtils.cpp | 220 ++++++++++++++++- ipc/dbus/DBusUtils.h | 69 +++++- ipc/dbus/RawDBusConnection.cpp | 10 +- ipc/dbus/RawDBusConnection.h | 7 +- 15 files changed, 848 insertions(+), 79 deletions(-) create mode 100644 dom/bluetooth/BluetoothUtils.h create mode 100644 dom/bluetooth/linux/BluetoothDBusUtils.cpp diff --git a/dom/bluetooth/BluetoothAdapter.cpp b/dom/bluetooth/BluetoothAdapter.cpp index d4990f63f838..f886be9df500 100644 --- a/dom/bluetooth/BluetoothAdapter.cpp +++ b/dom/bluetooth/BluetoothAdapter.cpp @@ -5,7 +5,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "BluetoothAdapter.h" -#include "BluetoothFirmware.h" +#include "BluetoothUtils.h" #include "nsDOMClassInfo.h" #include "nsDOMEvent.h" @@ -36,3 +36,29 @@ NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) NS_IMPL_ADDREF_INHERITED(BluetoothAdapter, nsDOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(BluetoothAdapter, nsDOMEventTargetHelper) +BluetoothAdapter::BluetoothAdapter(const nsCString& name) : + mName(name) +{ +} + +BluetoothAdapter::~BluetoothAdapter() +{ + if (NS_FAILED(UnregisterBluetoothEventHandler(mName, this))) { + NS_WARNING("Failed to unregister object with observer!"); + } +} + +// static +already_AddRefed +BluetoothAdapter::Create(const nsCString& name) { + nsRefPtr adapter = new BluetoothAdapter(name); + if (NS_FAILED(RegisterBluetoothEventHandler(name, adapter))) { + NS_WARNING("Failed to register object with observer!"); + return NULL; + } + return adapter.forget(); +} + +void BluetoothAdapter::Notify(const BluetoothEvent& aData) { + printf("Got an adapter message!\n"); +} diff --git a/dom/bluetooth/BluetoothAdapter.h b/dom/bluetooth/BluetoothAdapter.h index 42a624c42822..399dad5cf0e4 100644 --- a/dom/bluetooth/BluetoothAdapter.h +++ b/dom/bluetooth/BluetoothAdapter.h @@ -11,6 +11,7 @@ #include "nsDOMEventTargetHelper.h" #include "nsIDOMBluetoothAdapter.h" #include "nsIDOMDOMRequest.h" +#include "mozilla/Observer.h" class nsIEventTarget; @@ -18,6 +19,7 @@ BEGIN_BLUETOOTH_NAMESPACE class BluetoothAdapter : public nsDOMEventTargetHelper , public nsIDOMBluetoothAdapter + , public BluetoothEventObserver { public: NS_DECL_ISUPPORTS_INHERITED @@ -28,6 +30,16 @@ public: NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothAdapter, nsDOMEventTargetHelper) + static already_AddRefed + Create(const nsCString& name); + + void Notify(const BluetoothEvent& aParam); +protected: + nsCString mName; +private: + BluetoothAdapter() {} + BluetoothAdapter(const nsCString& name); + ~BluetoothAdapter(); }; END_BLUETOOTH_NAMESPACE diff --git a/dom/bluetooth/BluetoothCommon.h b/dom/bluetooth/BluetoothCommon.h index e74435926527..d11bb0be367e 100644 --- a/dom/bluetooth/BluetoothCommon.h +++ b/dom/bluetooth/BluetoothCommon.h @@ -7,6 +7,10 @@ #ifndef mozilla_dom_bluetooth_bluetoothcommon_h__ #define mozilla_dom_bluetooth_bluetoothcommon_h__ +#include "nsString.h" +#include "nsTArray.h" +#include "mozilla/Observer.h" + #define BEGIN_BLUETOOTH_NAMESPACE \ namespace mozilla { namespace dom { namespace bluetooth { #define END_BLUETOOTH_NAMESPACE \ @@ -14,6 +18,48 @@ #define USING_BLUETOOTH_NAMESPACE \ using namespace mozilla::dom::bluetooth; -class nsIDOMBluetooth; +class nsCString; + +BEGIN_BLUETOOTH_NAMESPACE + +/** + * BluetoothEvents usually hand back one of 3 types: + * + * - 32-bit Int + * - String + * - Bool + * + * BluetoothVariant encases the types into a single structure. + */ +struct BluetoothVariant +{ + uint32_t mUint32; + nsCString mString; +}; + +/** + * BluetoothNamedVariant is a variant with a name value, for passing around + * things like properties with variant values. + */ +struct BluetoothNamedVariant +{ + nsCString mName; + BluetoothVariant mValue; +}; + +/** + * BluetoothEvent holds a variant value and the name of an event, such as + * PropertyChanged or DeviceFound. + */ +struct BluetoothEvent +{ + nsCString mEventName; + nsTArray mValues; +}; + +typedef mozilla::Observer BluetoothEventObserver; +typedef mozilla::ObserverList BluetoothEventObserverList; + +END_BLUETOOTH_NAMESPACE #endif // mozilla_dom_bluetooth_bluetoothcommon_h__ diff --git a/dom/bluetooth/BluetoothManager.cpp b/dom/bluetooth/BluetoothManager.cpp index e9e5faec292d..564790e3f03c 100644 --- a/dom/bluetooth/BluetoothManager.cpp +++ b/dom/bluetooth/BluetoothManager.cpp @@ -8,6 +8,7 @@ #include "BluetoothCommon.h" #include "BluetoothFirmware.h" #include "BluetoothAdapter.h" +#include "BluetoothUtils.h" #include "nsIDocument.h" #include "nsIURI.h" @@ -30,6 +31,7 @@ #define DOM_BLUETOOTH_URL_PREF "dom.mozBluetooth.whitelist" +using namespace mozilla; using mozilla::Preferences; USING_BLUETOOTH_NAMESPACE @@ -37,17 +39,19 @@ USING_BLUETOOTH_NAMESPACE static void FireEnabled(bool aResult, nsIDOMDOMRequest* aDomRequest) { - nsCOMPtr rs = do_GetService("@mozilla.org/dom/dom-request-service;1"); + nsCOMPtr rs = + do_GetService("@mozilla.org/dom/dom-request-service;1"); if (!rs) { NS_WARNING("No DOMRequest Service!"); return; } - mozilla::DebugOnly rv = aResult ? - rs->FireSuccess(aDomRequest, JSVAL_VOID) : - rs->FireError(aDomRequest, - NS_LITERAL_STRING("Bluetooth firmware loading failed")); + DebugOnly rv = + aResult ? + rs->FireSuccess(aDomRequest, JSVAL_VOID) : + rs->FireError(aDomRequest, + NS_LITERAL_STRING("Bluetooth firmware loading failed")); NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Bluetooth firmware loading failed"); } @@ -120,7 +124,8 @@ class ToggleBtResultTask : public nsRunnable class ToggleBtTask : public nsRunnable { public: - ToggleBtTask(bool aEnabled, nsIDOMDOMRequest* aReq, BluetoothManager* aManager) + ToggleBtTask(bool aEnabled, nsIDOMDOMRequest* aReq, + BluetoothManager* aManager) : mEnabled(aEnabled), mManagerPtr(aManager), mDOMRequest(aReq) @@ -177,11 +182,19 @@ class ToggleBtTask : public nsRunnable }; BluetoothManager::BluetoothManager(nsPIDOMWindow *aWindow) : - mEnabled(false) + mEnabled(false), + mName(nsDependentCString("/")) { BindToOwner(aWindow); } +BluetoothManager::~BluetoothManager() +{ + if(NS_FAILED(UnregisterBluetoothEventHandler(mName, this))) { + NS_WARNING("Failed to unregister object with observer!"); + } +} + NS_IMETHODIMP BluetoothManager::SetEnabled(bool aEnabled, nsIDOMDOMRequest** aDomRequest) { @@ -220,12 +233,32 @@ BluetoothManager::GetEnabled(bool* aEnabled) NS_IMETHODIMP BluetoothManager::GetDefaultAdapter(nsIDOMBluetoothAdapter** aAdapter) { - //TODO: Implement adapter fetching - return NS_ERROR_FAILURE; + nsCString path; + nsresult rv = GetDefaultAdapterPathInternal(path); + if(NS_FAILED(rv)) { + NS_WARNING("Cannot fetch adapter path!"); + return NS_ERROR_FAILURE; + } + nsRefPtr adapter = BluetoothAdapter::Create(path); + adapter.forget(aAdapter); + return NS_OK; +} + +// static +already_AddRefed +BluetoothManager::Create(nsPIDOMWindow* aWindow) { + nsRefPtr manager = new BluetoothManager(aWindow); + nsDependentCString name("/"); + if(NS_FAILED(RegisterBluetoothEventHandler(name, manager))) { + NS_WARNING("Failed to register object with observer!"); + return NULL; + } + return manager.forget(); } nsresult -NS_NewBluetoothManager(nsPIDOMWindow* aWindow, nsIDOMBluetoothManager** aBluetoothManager) +NS_NewBluetoothManager(nsPIDOMWindow* aWindow, + nsIDOMBluetoothManager** aBluetoothManager) { NS_ASSERTION(aWindow, "Null pointer!"); @@ -238,8 +271,12 @@ NS_NewBluetoothManager(nsPIDOMWindow* aWindow, nsIDOMBluetoothManager** aBluetoo return NS_OK; } - nsRefPtr bluetoothManager = new BluetoothManager(aWindow); + nsRefPtr bluetoothManager = BluetoothManager::Create(aWindow); bluetoothManager.forget(aBluetoothManager); return NS_OK; } + +void BluetoothManager::Notify(const BluetoothEvent& aData) { + printf("Received an manager message!\n"); +} diff --git a/dom/bluetooth/BluetoothManager.h b/dom/bluetooth/BluetoothManager.h index d94af69b6f71..0239d4870467 100644 --- a/dom/bluetooth/BluetoothManager.h +++ b/dom/bluetooth/BluetoothManager.h @@ -10,13 +10,14 @@ #include "BluetoothCommon.h" #include "nsDOMEventTargetHelper.h" #include "nsIDOMBluetoothManager.h" +#include "nsWeakReference.h" +#include "mozilla/Observer.h" BEGIN_BLUETOOTH_NAMESPACE -class BluetoothAdapter; - class BluetoothManager : public nsDOMEventTargetHelper - , public nsIDOMBluetoothManager + , public nsIDOMBluetoothManager + , public BluetoothEventObserver { public: NS_DECL_ISUPPORTS_INHERITED @@ -27,11 +28,18 @@ public: NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothManager, nsDOMEventTargetHelper) - BluetoothManager(nsPIDOMWindow*); + inline void SetEnabledInternal(bool aEnabled) {mEnabled = aEnabled;} - + + static already_AddRefed + Create(nsPIDOMWindow* aWindow); + void Notify(const BluetoothEvent& aData); private: + BluetoothManager() {} + BluetoothManager(nsPIDOMWindow* aWindow); + ~BluetoothManager(); bool mEnabled; + nsCString mName; NS_DECL_EVENT_HANDLER(enabled) @@ -40,6 +48,7 @@ private: END_BLUETOOTH_NAMESPACE -nsresult NS_NewBluetoothManager(nsPIDOMWindow* aWindow, nsIDOMBluetoothManager** aBluetoothManager); +nsresult NS_NewBluetoothManager(nsPIDOMWindow* aWindow, + nsIDOMBluetoothManager** aBluetoothManager); #endif diff --git a/dom/bluetooth/BluetoothUtils.h b/dom/bluetooth/BluetoothUtils.h new file mode 100644 index 000000000000..169c4d123ca0 --- /dev/null +++ b/dom/bluetooth/BluetoothUtils.h @@ -0,0 +1,77 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=40: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_bluetooth_bluetoothutils_h__ +#define mozilla_dom_bluetooth_bluetoothutils_h__ + +#include "BluetoothCommon.h" + +BEGIN_BLUETOOTH_NAMESPACE + +/** + * BluetoothUtil functions are used to dispatch messages to Bluetooth DOM + * objects on the main thread, as well as provide platform indenpendent access + * to BT functionality. Tasks for polling for outside messages will usually + * happen on the IO Thread (see ipc/dbus for instance), and these messages will + * be encased in runnables that will then be distributed via observers managed + * here. + */ + +/** + * Add a message handler object from message distribution observer. + * Must be called from the main thread. + * + * @param aNodeName Node name of the object + * @param aMsgHandler Weak pointer to the object + * + * @return NS_OK on successful addition to observer, NS_ERROR_FAILED + * otherwise + */ +nsresult RegisterBluetoothEventHandler(const nsCString& aNodeName, + BluetoothEventObserver *aMsgHandler); + +/** + * Remove a message handler object from message distribution observer. + * Must be called from the main thread. + * + * @param aNodeName Node name of the object + * @param aMsgHandler Weak pointer to the object + * + * @return NS_OK on successful removal from observer service, + * NS_ERROR_FAILED otherwise + */ +nsresult UnregisterBluetoothEventHandler(const nsCString& aNodeName, + BluetoothEventObserver *aMsgHandler); + +/** + * Returns the path of the default adapter, implemented via a platform + * specific method. + * + * @return Default adapter path/name on success, NULL otherwise + */ +nsresult GetDefaultAdapterPathInternal(nsCString& aAdapterPath); + +/** + * Set up variables and start the platform specific connection. Must + * be called from main thread. + * + * @return NS_OK if connection starts successfully, NS_ERROR_FAILURE + * otherwise + */ +nsresult StartBluetoothConnection(); + +/** + * Stop the platform specific connection. Must be called from main + * thread. + * + * @return NS_OK if connection starts successfully, NS_ERROR_FAILURE + * otherwise + */ +nsresult StopBluetoothConnection(); + +END_BLUETOOTH_NAMESPACE + +#endif diff --git a/dom/bluetooth/Makefile.in b/dom/bluetooth/Makefile.in index 0b8c26807d1b..8007ef6b05fe 100644 --- a/dom/bluetooth/Makefile.in +++ b/dom/bluetooth/Makefile.in @@ -15,6 +15,16 @@ XPIDL_MODULE = dom_bluetooth LIBXUL_LIBRARY = 1 FORCE_STATIC_LIB = 1 +ifeq (gonk,$(MOZ_WIDGET_TOOLKIT)) +VPATH += $(srcdir)/linux +LOCAL_INCLUDES += $(MOZ_DBUS_CFLAGS) +endif + +ifdef MOZ_ENABLE_DBUS +VPATH += $(srcdir)/linux +LOCAL_INCLUDES += $(MOZ_DBUS_CFLAGS) +endif + include $(topsrcdir)/dom/dom-config.mk CPPSRCS = \ @@ -23,23 +33,23 @@ CPPSRCS = \ BluetoothFirmware.cpp \ $(NULL) +ifdef MOZ_ENABLE_DBUS +CPPSRCS += BluetoothDBusUtils.cpp +endif + +ifeq (gonk,$(MOZ_WIDGET_TOOLKIT)) +CPPSRCS += BluetoothDBusUtils.cpp +endif + XPIDLSRCS = \ nsIDOMNavigatorBluetooth.idl \ nsIDOMBluetoothManager.idl \ nsIDOMBluetoothAdapter.idl \ $(NULL) -ifneq (gonk,$(MOZ_WIDGET_TOOLKIT)) -CFLAGS += $(MOZ_DBUS_CFLAGS) -CXXFLAGS += $(MOZ_DBUS_CFLAGS) -DHAVE_PTHREADS -endif - include $(topsrcdir)/config/rules.mk -ifeq (Linux,$(OS_TARGET)) ifdef MOZ_ENABLE_DBUS CFLAGS += $(MOZ_DBUS_GLIB_CFLAGS) CXXFLAGS += $(MOZ_DBUS_GLIB_CFLAGS) -DHAVE_PTHREADS endif -endif - diff --git a/dom/bluetooth/linux/BluetoothDBusUtils.cpp b/dom/bluetooth/linux/BluetoothDBusUtils.cpp new file mode 100644 index 000000000000..85b117a9050e --- /dev/null +++ b/dom/bluetooth/linux/BluetoothDBusUtils.cpp @@ -0,0 +1,270 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=40: */ +/* +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "BluetoothUtils.h" + +#include +#include + +#include "nsAutoPtr.h" +#include "nsThreadUtils.h" +#include "nsDebug.h" +#include "nsClassHashtable.h" +#include "mozilla/ipc/DBusUtils.h" +#include "mozilla/ipc/RawDBusConnection.h" + + +using namespace mozilla::ipc; + +namespace mozilla { +namespace dom { +namespace bluetooth { + +static nsAutoPtr sDBusConnection; + +#undef LOG +#if defined(MOZ_WIDGET_GONK) +#include +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GonkDBus", args); +#else +#define BTDEBUG true +#define LOG(args...) if (BTDEBUG) printf(args); +#endif + +#define DBUS_ADAPTER_IFACE BLUEZ_DBUS_BASE_IFC ".Adapter" +#define DBUS_DEVICE_IFACE BLUEZ_DBUS_BASE_IFC ".Device" +#define BLUEZ_DBUS_BASE_PATH "/org/bluez" +#define BLUEZ_DBUS_BASE_IFC "org.bluez" +#define BLUEZ_ERROR_IFC "org.bluez.Error" + +static const char* BLUETOOTH_DBUS_SIGNALS[] = +{ + "type='signal',interface='org.freedesktop.DBus'", + "type='signal',interface='org.bluez.Adapter'", + "type='signal',interface='org.bluez.Manager'", + "type='signal',interface='org.bluez.Device'", + "type='signal',interface='org.bluez.Input'", + "type='signal',interface='org.bluez.Network'", + "type='signal',interface='org.bluez.NetworkServer'", + "type='signal',interface='org.bluez.HealthDevice'", + "type='signal',interface='org.bluez.AudioSink'" +}; + +typedef nsClassHashtable + BluetoothEventObserverTable; +static nsAutoPtr sBluetoothEventObserverTable; + +nsresult +RegisterBluetoothEventHandler(const nsCString& aNodeName, + BluetoothEventObserver* aHandler) +{ + MOZ_ASSERT(NS_IsMainThread()); + BluetoothEventObserverList *ol; + if (!sBluetoothEventObserverTable->Get(aNodeName, &ol)) { + sBluetoothEventObserverTable->Put(aNodeName, + new BluetoothEventObserverList()); + } + sBluetoothEventObserverTable->Get(aNodeName, &ol); + ol->AddObserver(aHandler); + return NS_OK; +} + +nsresult +UnregisterBluetoothEventHandler(const nsCString& aNodeName, + BluetoothEventObserver* aHandler) +{ + MOZ_ASSERT(NS_IsMainThread()); + BluetoothEventObserverList *ol; + if (!sBluetoothEventObserverTable->Get(aNodeName, &ol)) { + NS_WARNING("Node does not exist to remove BluetoothEventListener from!"); + return NS_ERROR_FAILURE; + } + sBluetoothEventObserverTable->Get(aNodeName, &ol); + ol->RemoveObserver(aHandler); + if (ol->Length() == 0) { + sBluetoothEventObserverTable->Remove(aNodeName); + } + return NS_OK; +} + +struct DistributeDBusMessageTask : public nsRunnable { + + DistributeDBusMessageTask(DBusMessage* aMsg) : mMsg(aMsg) + { + } + + NS_IMETHOD Run() + { + if (dbus_message_get_path(mMsg.get()) == NULL) { + return NS_OK; + } + MOZ_ASSERT(NS_IsMainThread()); + + // Notify observers that a message has been sent + nsDependentCString path(dbus_message_get_path(mMsg.get())); + nsDependentCString member(dbus_message_get_member(mMsg.get())); + BluetoothEventObserverList *ol; + if (!sBluetoothEventObserverTable->Get(path, &ol)) { + LOG("No objects registered for %s, returning\n", + dbus_message_get_path(mMsg.get())); + return NS_OK; + } + BluetoothEvent e; + e.mEventName = member; + ol->Broadcast(e); + return NS_OK; + } + + DBusMessageRefPtr mMsg; +}; + +// Called by dbus during WaitForAndDispatchEventNative() +// This function is called on the IOThread +static DBusHandlerResult +EventFilter(DBusConnection *aConn, DBusMessage *aMsg, + void *aData) +{ + DBusError err; + + dbus_error_init(&err); + + if (dbus_message_get_type(aMsg) != DBUS_MESSAGE_TYPE_SIGNAL) { + LOG("%s: not interested (not a signal).\n", __FUNCTION__); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + LOG("%s: Received signal %s:%s from %s\n", __FUNCTION__, + dbus_message_get_interface(aMsg), dbus_message_get_member(aMsg), + dbus_message_get_path(aMsg)); + + // TODO: Parse DBusMessage* on the IOThread and return as a BluetoothEvent so + // we aren't passing the pointer at all, as well as offloading parsing (not + // that it's that heavy.) + nsCOMPtr t(new DistributeDBusMessageTask(aMsg)); + if (NS_FAILED(NS_DispatchToMainThread(t))) { + NS_WARNING("Failed to dispatch to main thread!"); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +nsresult +StartBluetoothConnection() +{ + if(sDBusConnection) { + NS_WARNING("DBusConnection already established, skipping"); + return NS_OK; + } + sBluetoothEventObserverTable = new BluetoothEventObserverTable(); + sBluetoothEventObserverTable->Init(100); + + sDBusConnection = new RawDBusConnection(); + sDBusConnection->EstablishDBusConnection(); + + // Add a filter for all incoming messages_base + if (!dbus_connection_add_filter(sDBusConnection->mConnection, EventFilter, + NULL, NULL)) { + NS_WARNING("Cannot create DBus Event Filter for DBus Thread!"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +StopBluetoothConnection() +{ + if(!sDBusConnection) { + NS_WARNING("DBusConnection does not exist, nothing to stop, skipping."); + return NS_OK; + } + dbus_connection_remove_filter(sDBusConnection->mConnection, EventFilter, NULL); + sDBusConnection = NULL; + sBluetoothEventObserverTable->Clear(); + sBluetoothEventObserverTable = NULL; + return NS_OK; +} + +nsresult +GetDefaultAdapterPathInternal(nsCString& aAdapterPath) +{ + DBusMessage *msg = NULL, *reply = NULL; + DBusError err; + const char *device_path = NULL; + int attempt = 0; + + for (attempt = 0; attempt < 1000 && reply == NULL; attempt ++) { + msg = dbus_message_new_method_call("org.bluez", "/", + "org.bluez.Manager", "DefaultAdapter"); + if (!msg) { + LOG("%s: Can't allocate new method call for get_adapter_path!", + __FUNCTION__); + return NS_ERROR_FAILURE; + } + dbus_message_append_args(msg, DBUS_TYPE_INVALID); + dbus_error_init(&err); + reply = dbus_connection_send_with_reply_and_block( + sDBusConnection->mConnection, msg, -1, &err); + + if (!reply) { + if (dbus_error_is_set(&err)) { + if (dbus_error_has_name(&err, + "org.freedesktop.DBus.Error.ServiceUnknown")) { + // bluetoothd is still down, retry + LOG("Service unknown\n"); + dbus_error_free(&err); + //usleep(10000); // 10 ms + continue; + } else if (dbus_error_has_name(&err, + "org.bluez.Error.NoSuchAdapter")) { + LOG("No adapter found\n"); + dbus_error_free(&err); + goto failed; + } else { + // Some other error we weren't expecting + LOG("other error\n"); + dbus_error_free(&err); + } + } + } + } + if (attempt == 1000) { + LOG("timeout\n"); + //printfE("Time out while trying to get Adapter path, is bluetoothd up ?"); + goto failed; + } + + if (!dbus_message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, + &device_path, DBUS_TYPE_INVALID) + || !device_path) { + if (dbus_error_is_set(&err)) { + dbus_error_free(&err); + } + goto failed; + } + dbus_message_unref(msg); + aAdapterPath = nsDependentCString(device_path); + return NS_OK; +failed: + dbus_message_unref(msg); + return NS_ERROR_FAILURE; +} + +} +} +} diff --git a/dom/system/gonk/SystemWorkerManager.cpp b/dom/system/gonk/SystemWorkerManager.cpp index 901670f4be2c..25fb4b4567a5 100644 --- a/dom/system/gonk/SystemWorkerManager.cpp +++ b/dom/system/gonk/SystemWorkerManager.cpp @@ -22,6 +22,7 @@ #ifdef MOZ_B2G_BT #include "mozilla/ipc/DBusThread.h" #include "BluetoothFirmware.h" +#include "BluetoothUtils.h" #endif #include "nsContentUtils.h" #include "nsServiceManagerUtils.h" @@ -234,6 +235,7 @@ SystemWorkerManager::Init() NS_WARNING("Failed to initialize Bluetooth!"); return rv; } + #endif #ifdef MOZ_WIDGET_GONK @@ -395,6 +397,7 @@ SystemWorkerManager::InitBluetooth(JSContext *cx) if(EnsureBluetoothInit()) { #endif StartDBus(); + StartBluetoothConnection(); #ifdef MOZ_WIDGET_GONK } else { diff --git a/ipc/dbus/DBusThread.cpp b/ipc/dbus/DBusThread.cpp index cf28e1533ab3..21c4c4bb58f4 100644 --- a/ipc/dbus/DBusThread.cpp +++ b/ipc/dbus/DBusThread.cpp @@ -45,18 +45,21 @@ #include "base/eintr_wrapper.h" #include "base/message_loop.h" #include "nsTArray.h" +#include "nsDataHashtable.h" #include "mozilla/RefPtr.h" #include "mozilla/Monitor.h" #include "mozilla/Util.h" #include "mozilla/FileUtils.h" -#include "nsAutoPtr.h" +#include "nsThreadUtils.h" #include "nsIThread.h" #include "nsXULAppAPI.h" +#include "nsServiceManagerUtils.h" +#include "nsCOMPtr.h" #undef LOG #if defined(MOZ_WIDGET_GONK) #include -#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GonkBluetooth", args); +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GonkDBus", args); #else #define BTDEBUG true #define LOG(args...) if(BTDEBUG) printf(args); @@ -82,6 +85,7 @@ static const char* DBUS_SIGNALS[] = { "type='signal',interface='org.freedesktop.DBus'", "type='signal',interface='org.bluez.Adapter'", + "type='signal',interface='org.bluez.Manager'", "type='signal',interface='org.bluez.Device'", "type='signal',interface='org.bluez.Input'", "type='signal',interface='org.bluez.Network'", @@ -291,27 +295,6 @@ static void HandleWatchRemove(DBusThread* aDbt) { aDbt->mWatchData.RemoveElementAt(index); } -// Called by dbus during WaitForAndDispatchEventNative() -static DBusHandlerResult -EventFilter(DBusConnection *aConn, DBusMessage *aMsg, - void *aData) -{ - DBusError err; - - dbus_error_init(&err); - - if (dbus_message_get_type(aMsg) != DBUS_MESSAGE_TYPE_SIGNAL) { - LOG("%s: not interested (not a signal).\n", __FUNCTION__); - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - } - - LOG("%s: Received signal %s:%s from %s\n", __FUNCTION__, - dbus_message_get_interface(aMsg), dbus_message_get_member(aMsg), - dbus_message_get_path(aMsg)); - - return DBUS_HANDLER_RESULT_HANDLED; -} - // DBus Thread Implementation DBusThread::DBusThread() : mMutex("DBusGonk.mMutex") @@ -337,12 +320,9 @@ DBusThread::SetUpEventLoop() dbus_error_init(&err); // If we can't establish a connection to dbus, nothing else will work - if(!Create()) { - return false; - } - - // Add a filter for all incoming messages_base - if (!dbus_connection_add_filter(mConnection, EventFilter, this, NULL)){ + nsresult rv = EstablishDBusConnection(); + if(NS_FAILED(rv)) { + NS_WARNING("Cannot create DBus Connection for DBus Thread!"); return false; } @@ -365,7 +345,7 @@ DBusThread::SetUpEventLoop() bool DBusThread::TearDownData() { - LOG("Removing DBus Bluetooth Sockets\n"); + LOG("Removing DBus Sockets\n"); if (mControlFdW.get()) { mControlFdW.dispose(); } @@ -397,7 +377,6 @@ DBusThread::TearDownEventLoop() } } - dbus_connection_remove_filter(mConnection, EventFilter, this); return true; } @@ -411,7 +390,7 @@ DBusThread::EventLoop(void *aPtr) RemoveWatch, ToggleWatch, aPtr, NULL); dbt->mIsRunning = true; - LOG("DBus Bluetooth Event Loop Starting\n"); + LOG("DBus Event Loop Starting\n"); while (1) { poll(dbt->mPollData.Elements(), dbt->mPollData.Length(), -1); @@ -427,7 +406,7 @@ DBusThread::EventLoop(void *aPtr) switch (data) { case DBUS_EVENT_LOOP_EXIT: { - LOG("DBus Bluetooth Event Loop Exiting\n"); + LOG("DBus Event Loop Exiting\n"); dbus_connection_set_watch_functions(dbt->mConnection, NULL, NULL, NULL, NULL, NULL); dbt->TearDownEventLoop(); @@ -495,7 +474,7 @@ DBusThread::StartEventLoop() TearDownData(); return false; } - LOG("DBus Bluetooth Thread Starting\n"); + LOG("DBus Thread Starting\n"); pthread_create(&(mThread), NULL, DBusThread::EventLoop, this); return true; } @@ -508,12 +487,12 @@ DBusThread::StopEventLoop() char data = DBUS_EVENT_LOOP_EXIT; ssize_t wret = write(mControlFdW.get(), &data, sizeof(char)); if(wret < 0) { - LOG("Cannot write exit bit to DBus Bluetooth Thread!\n"); + LOG("Cannot write exit bit to DBus Thread!\n"); } void *ret; - LOG("DBus Bluetooth Thread Joining\n"); + LOG("DBus Thread Joining\n"); pthread_join(mThread, &ret); - LOG("DBus Bluetooth Thread Joined\n"); + LOG("DBus Thread Joined\n"); TearDownData(); } mIsRunning = false; @@ -535,6 +514,7 @@ ConnectDBus(Monitor* aMonitor, bool* aSuccess) NS_WARNING("Trying to start DBus Thread that is already currently running, skipping."); return; } + sDBusThread = new DBusThread(); *aSuccess = true; if(!sDBusThread->StartEventLoop()) @@ -554,6 +534,7 @@ DisconnectDBus(Monitor* aMonitor, bool* aSuccess) NS_WARNING("Trying to shutdown DBus Thread that is not currently running, skipping."); return; } + *aSuccess = true; sDBusThread->StopEventLoop(); sDBusThread = NULL; diff --git a/ipc/dbus/DBusThread.h b/ipc/dbus/DBusThread.h index 7e0942c0bd11..ad8a6ab79aa3 100644 --- a/ipc/dbus/DBusThread.h +++ b/ipc/dbus/DBusThread.h @@ -7,18 +7,30 @@ #ifndef mozilla_ipc_dbus_gonk_dbusthread_h__ #define mozilla_ipc_dbus_gonk_dbusthread_h__ +struct DBusMessage; + namespace mozilla { namespace ipc { +class nsCString; -// Starts the DBus thread, which handles returning signals to objects -// that call asynchronous functions. This should be called from the -// main thread at startup. +/** + * Starts the DBus thread, which handles returning signals to objects + * that call asynchronous functions. This should be called from the + * main thread at startup. + * + * @return True on thread starting correctly, false otherwise + */ bool StartDBus(); -// Stop the DBus thread, assuming it's currently running. Should be -// called from main thread. +/** + * Stop the DBus thread, assuming it's currently running. Should be + * called from main thread. + * + * @return True on thread stopping correctly, false otherwise + */ bool StopDBus(); + } } #endif diff --git a/ipc/dbus/DBusUtils.cpp b/ipc/dbus/DBusUtils.cpp index 3d1d6972bb11..2c99e2d33ee7 100644 --- a/ipc/dbus/DBusUtils.cpp +++ b/ipc/dbus/DBusUtils.cpp @@ -16,8 +16,9 @@ ** limitations under the License. */ -#include -#include "dbus/dbus.h" +#include "DBusUtils.h" +#include +#include #undef LOG #if defined(MOZ_WIDGET_GONK) @@ -27,6 +28,10 @@ #define LOG(args...) printf(args); #endif +#define BLUEZ_DBUS_BASE_PATH "/org/bluez" +#define BLUEZ_DBUS_BASE_IFC "org.bluez" +#define BLUEZ_ERROR_IFC "org.bluez.Error" + namespace mozilla { namespace ipc { @@ -43,5 +48,216 @@ log_and_free_dbus_error(DBusError* err, const char* function, DBusMessage* msg) dbus_error_free((err)); } +typedef struct { + void (*user_cb)(DBusMessage *, void *, void *); + void *user; + void *nat; +} dbus_async_call_t; + +void dbus_func_args_async_callback(DBusPendingCall *call, void *data) { + + dbus_async_call_t *req = (dbus_async_call_t *)data; + DBusMessage *msg; + + /* This is guaranteed to be non-NULL, because this function is called only + when once the remote method invokation returns. */ + msg = dbus_pending_call_steal_reply(call); + + if (msg) { + if (req->user_cb) { + // The user may not deref the message object. + req->user_cb(msg, req->user, req->nat); + } + dbus_message_unref(msg); + } + + //dbus_message_unref(req->method); + dbus_pending_call_cancel(call); + dbus_pending_call_unref(call); + free(req); +} + +static dbus_bool_t dbus_func_args_async_valist(DBusConnection *conn, + int timeout_ms, + void (*user_cb)(DBusMessage *, + void *, + void*), + void *user, + void *nat, + const char *path, + const char *ifc, + const char *func, + int first_arg_type, + va_list args) { + DBusMessage *msg = NULL; + const char *name; + dbus_async_call_t *pending; + dbus_bool_t reply = FALSE; + + /* Compose the command */ + msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, path, ifc, func); + + if (msg == NULL) { + LOG("Could not allocate D-Bus message object!"); + goto done; + } + + /* append arguments */ + if (!dbus_message_append_args_valist(msg, first_arg_type, args)) { + LOG("Could not append argument to method call!"); + goto done; + } + + /* Make the call. */ + pending = (dbus_async_call_t *)malloc(sizeof(dbus_async_call_t)); + if (pending) { + DBusPendingCall *call; + + pending->user_cb = user_cb; + pending->user = user; + pending->nat = nat; + //pending->method = msg; + + reply = dbus_connection_send_with_reply(conn, msg, + &call, + timeout_ms); + if (reply == TRUE) { + dbus_pending_call_set_notify(call, + dbus_func_args_async_callback, + pending, + NULL); + } + } + +done: + if (msg) dbus_message_unref(msg); + return reply; +} + +dbus_bool_t dbus_func_args_async(DBusConnection *conn, + int timeout_ms, + void (*reply)(DBusMessage *, void *, void*), + void *user, + void *nat, + const char *path, + const char *ifc, + const char *func, + int first_arg_type, + ...) { + dbus_bool_t ret; + va_list lst; + va_start(lst, first_arg_type); + + ret = dbus_func_args_async_valist(conn, + timeout_ms, + reply, user, nat, + path, ifc, func, + first_arg_type, lst); + va_end(lst); + return ret; +} + +// If err is NULL, then any errors will be LOG'd, and free'd and the reply +// will be NULL. +// If err is not NULL, then it is assumed that dbus_error_init was already +// called, and error's will be returned to the caller without logging. The +// return value is NULL iff an error was set. The client must free the error if +// set. +DBusMessage * dbus_func_args_timeout_valist(DBusConnection *conn, + int timeout_ms, + DBusError *err, + const char *path, + const char *ifc, + const char *func, + int first_arg_type, + va_list args) { + + DBusMessage *msg = NULL, *reply = NULL; + const char *name; + bool return_error = (err != NULL); + + if (!return_error) { + err = (DBusError*)malloc(sizeof(DBusError)); + dbus_error_init(err); + } + + /* Compose the command */ + msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, path, ifc, func); + + if (msg == NULL) { + LOG("Could not allocate D-Bus message object!"); + goto done; + } + + /* append arguments */ + if (!dbus_message_append_args_valist(msg, first_arg_type, args)) { + LOG("Could not append argument to method call!"); + goto done; + } + + /* Make the call. */ + reply = dbus_connection_send_with_reply_and_block(conn, msg, timeout_ms, err); + if (!return_error && dbus_error_is_set(err)) { + //LOG_AND_FREE_DBUS_ERROR_WITH_MSG(err, msg); + } + +done: + if (!return_error) { + free(err); + } + if (msg) dbus_message_unref(msg); + return reply; +} + +DBusMessage * dbus_func_args_timeout(DBusConnection *conn, + int timeout_ms, + const char *path, + const char *ifc, + const char *func, + int first_arg_type, + ...) { + DBusMessage *ret; + va_list lst; + va_start(lst, first_arg_type); + ret = dbus_func_args_timeout_valist(conn, timeout_ms, NULL, + path, ifc, func, + first_arg_type, lst); + va_end(lst); + return ret; +} + +DBusMessage * dbus_func_args(DBusConnection *conn, + const char *path, + const char *ifc, + const char *func, + int first_arg_type, + ...) { + DBusMessage *ret; + va_list lst; + va_start(lst, first_arg_type); + ret = dbus_func_args_timeout_valist(conn, -1, NULL, + path, ifc, func, + first_arg_type, lst); + va_end(lst); + return ret; +} + +DBusMessage * dbus_func_args_error(DBusConnection *conn, + DBusError *err, + const char *path, + const char *ifc, + const char *func, + int first_arg_type, + ...) { + DBusMessage *ret; + va_list lst; + va_start(lst, first_arg_type); + ret = dbus_func_args_timeout_valist(conn, -1, err, + path, ifc, func, + first_arg_type, lst); + va_end(lst); + return ret; +} + } } diff --git a/ipc/dbus/DBusUtils.h b/ipc/dbus/DBusUtils.h index c7d90ade578b..b0cb3a7ad3c6 100644 --- a/ipc/dbus/DBusUtils.h +++ b/ipc/dbus/DBusUtils.h @@ -19,6 +19,9 @@ #ifndef mozilla_ipc_dbus_dbusutils_h__ #define mozilla_ipc_dbus_dbusutils_h__ +#include +#include "mozilla/Scoped.h" + // LOGE and free a D-Bus error // Using #define so that __FUNCTION__ resolves usefully #define LOG_AND_FREE_DBUS_ERROR_WITH_MSG(err, msg) log_and_free_dbus_error(err, __FUNCTION__, msg); @@ -29,7 +32,71 @@ struct DBusError; namespace mozilla { namespace ipc { -void log_and_free_dbus_error(DBusError* err, const char* function, DBusMessage* msg = NULL); + +class DBusMessageRefPtr +{ +public: + DBusMessageRefPtr(DBusMessage* aMsg) : mMsg(aMsg) + { + if (mMsg) dbus_message_ref(mMsg); + } + ~DBusMessageRefPtr() + { + if (mMsg) dbus_message_unref(mMsg); + } + operator DBusMessage*() { return mMsg; } + DBusMessage* get() { return mMsg; } +private: + DBusMessage* mMsg; +}; + +void log_and_free_dbus_error(DBusError* err, + const char* function, + DBusMessage* msg = NULL); +dbus_bool_t dbus_func_args_async(DBusConnection *conn, + int timeout_ms, + void (*reply)(DBusMessage *, void *, void *), + void *user, + void *nat, + const char *path, + const char *ifc, + const char *func, + int first_arg_type, + ...); + +DBusMessage * dbus_func_args(DBusConnection *conn, + const char *path, + const char *ifc, + const char *func, + int first_arg_type, + ...); + +DBusMessage * dbus_func_args_error(DBusConnection *conn, + DBusError *err, + const char *path, + const char *ifc, + const char *func, + int first_arg_type, + ...); + +DBusMessage * dbus_func_args_timeout(DBusConnection *conn, + int timeout_ms, + const char *path, + const char *ifc, + const char *func, + int first_arg_type, + ...); + +DBusMessage * dbus_func_args_timeout_valist(DBusConnection *conn, + int timeout_ms, + DBusError *err, + const char *path, + const char *ifc, + const char *func, + int first_arg_type, + va_list args); + + } } diff --git a/ipc/dbus/RawDBusConnection.cpp b/ipc/dbus/RawDBusConnection.cpp index 6b92ae3c7295..0a7b2b30a689 100644 --- a/ipc/dbus/RawDBusConnection.cpp +++ b/ipc/dbus/RawDBusConnection.cpp @@ -15,15 +15,19 @@ RawDBusConnection::RawDBusConnection() { RawDBusConnection::~RawDBusConnection() { } -bool RawDBusConnection::Create() { +nsresult RawDBusConnection::EstablishDBusConnection() { DBusError err; dbus_error_init(&err); mConnection = dbus_bus_get(DBUS_BUS_SYSTEM, &err); if (dbus_error_is_set(&err)) { dbus_error_free(&err); - return false; + return NS_ERROR_FAILURE; } dbus_connection_set_exit_on_disconnect(mConnection, FALSE); - return true; + return NS_OK; } +void RawDBusConnection::ScopedDBusConnectionPtrTraits::release(DBusConnection* ptr) +{ + if(ptr) dbus_connection_unref(ptr); +} diff --git a/ipc/dbus/RawDBusConnection.h b/ipc/dbus/RawDBusConnection.h index 6ae6498177ac..f407069b76ea 100644 --- a/ipc/dbus/RawDBusConnection.h +++ b/ipc/dbus/RawDBusConnection.h @@ -12,8 +12,8 @@ #include #include #include +#include "nscore.h" #include "mozilla/Scoped.h" -#include "dbus/dbus.h" struct DBusConnection; @@ -24,14 +24,13 @@ class RawDBusConnection { struct ScopedDBusConnectionPtrTraits : ScopedFreePtrTraits { - static void release(DBusConnection* ptr) { if(ptr) dbus_connection_unref(ptr); } + static void release(DBusConnection* ptr); }; public: RawDBusConnection(); ~RawDBusConnection(); - bool Create(); -protected: + nsresult EstablishDBusConnection(); Scoped mConnection; }; From 43e094f32a41be1cd371eadc987b938b5402fcb5 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Sat, 2 Jun 2012 14:23:45 -0400 Subject: [PATCH 06/14] Bug 750684 - Add new special bookmarks folder to hold reading list items (r=margaret) --- .../android/base/db/BrowserContract.java.in | 6 ++ .../android/base/db/BrowserProvider.java.in | 80 ++++++++++++++++++- .../base/locales/en-US/sync_strings.dtd | 1 + .../base/tests/testBrowserProvider.java.in | 80 ++++++++++++++++++- mobile/android/sync/strings.xml.in | 1 + 5 files changed, 162 insertions(+), 6 deletions(-) diff --git a/mobile/android/base/db/BrowserContract.java.in b/mobile/android/base/db/BrowserContract.java.in index c6aaff0cfd96..9cc009af45d0 100644 --- a/mobile/android/base/db/BrowserContract.java.in +++ b/mobile/android/base/db/BrowserContract.java.in @@ -76,6 +76,7 @@ public class BrowserContract { public static final int FIXED_ROOT_ID = 0; public static final int FAKE_DESKTOP_FOLDER_ID = -1; + public static final int FIXED_READING_LIST_ID = -2; public static final String MOBILE_FOLDER_GUID = "mobile"; public static final String PLACES_FOLDER_GUID = "places"; @@ -83,6 +84,7 @@ public class BrowserContract { public static final String TAGS_FOLDER_GUID = "tags"; public static final String TOOLBAR_FOLDER_GUID = "toolbar"; public static final String UNFILED_FOLDER_GUID = "unfiled"; + public static final String READING_LIST_FOLDER_GUID = "readinglist"; public static final String FAKE_DESKTOP_FOLDER_GUID = "desktop"; public static final int TYPE_FOLDER = 0; @@ -118,8 +120,12 @@ public class BrowserContract { private Combined() {} public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "combined"); + public static final int DISPLAY_NORMAL = 0; + public static final int DISPLAY_READER = 1; + public static final String BOOKMARK_ID = "bookmark_id"; public static final String HISTORY_ID = "history_id"; + public static final String DISPLAY = "display"; } public static final class Schema { diff --git a/mobile/android/base/db/BrowserProvider.java.in b/mobile/android/base/db/BrowserProvider.java.in index 685d21d44ef7..b218e8d4bc4a 100644 --- a/mobile/android/base/db/BrowserProvider.java.in +++ b/mobile/android/base/db/BrowserProvider.java.in @@ -67,7 +67,7 @@ public class BrowserProvider extends ContentProvider { static final String DATABASE_NAME = "browser.db"; - static final int DATABASE_VERSION = 8; + static final int DATABASE_VERSION = 9; // Maximum age of deleted records to be cleaned up (20 days in ms) static final long MAX_AGE_OF_DELETED_RECORDS = 86400000 * 20; @@ -209,6 +209,7 @@ public class BrowserProvider extends ContentProvider { map.put(Combined._ID, Combined._ID); map.put(Combined.BOOKMARK_ID, Combined.BOOKMARK_ID); map.put(Combined.HISTORY_ID, Combined.HISTORY_ID); + map.put(Combined.DISPLAY, Combined.DISPLAY); map.put(Combined.URL, Combined.URL); map.put(Combined.TITLE, Combined.TITLE); map.put(Combined.VISITS, Combined.VISITS); @@ -435,6 +436,64 @@ public class BrowserProvider extends ContentProvider { " ON " + Combined.URL + " = " + qualifyColumn(TABLE_IMAGES, Images.URL)); } + private void createCombinedWithImagesViewOn9(SQLiteDatabase db) { + debug("Creating " + VIEW_COMBINED_WITH_IMAGES + " view"); + + db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_IMAGES + " AS" + + " SELECT " + Combined.BOOKMARK_ID + ", " + + Combined.HISTORY_ID + ", " + + // We need to return an _id column because CursorAdapter requires it for its + // default implementation for the getItemId() method. However, since + // we're not using this feature in the parts of the UI using this view, + // we can just use 0 for all rows. + "0 AS " + Combined._ID + ", " + + Combined.URL + ", " + + Combined.TITLE + ", " + + Combined.VISITS + ", " + + Combined.DISPLAY + ", " + + Combined.DATE_LAST_VISITED + ", " + + qualifyColumn(TABLE_IMAGES, Images.FAVICON) + " AS " + Combined.FAVICON + ", " + + qualifyColumn(TABLE_IMAGES, Images.THUMBNAIL) + " AS " + Combined.THUMBNAIL + + " FROM (" + + // Bookmarks without history. + " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " + + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + + Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " + + Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " + + "-1 AS " + Combined.HISTORY_ID + ", " + + "-1 AS " + Combined.VISITS + ", " + + "-1 AS " + Combined.DATE_LAST_VISITED + + " FROM " + TABLE_BOOKMARKS + + " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + + " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" + + " UNION ALL" + + // History with and without bookmark. + " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " + + qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " + + // Prioritze bookmark titles over history titles, since the user may have + // customized the title for a bookmark. + "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " + + qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " + + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + + Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " + + Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " + + qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " + + qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " + + qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + + " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS + + " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) + + " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " + + qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND (" + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + ")" + + ") LEFT OUTER JOIN " + TABLE_IMAGES + + " ON " + Combined.URL + " = " + qualifyColumn(TABLE_IMAGES, Images.URL)); + } + @Override public void onCreate(SQLiteDatabase db) { debug("Creating browser.db: " + db.getPath()); @@ -445,7 +504,7 @@ public class BrowserProvider extends ContentProvider { createBookmarksWithImagesView(db); createHistoryWithImagesView(db); - createCombinedWithImagesView(db); + createCombinedWithImagesViewOn9(db); createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID, R.string.bookmarks_folder_places, 0); @@ -539,6 +598,8 @@ public class BrowserProvider extends ContentProvider { R.string.bookmarks_folder_tags, 3); createOrUpdateSpecialFolder(db, Bookmarks.UNFILED_FOLDER_GUID, R.string.bookmarks_folder_unfiled, 4); + createOrUpdateSpecialFolder(db, Bookmarks.READING_LIST_FOLDER_GUID, + R.string.bookmarks_folder_reading_list, 5); } private void createOrUpdateSpecialFolder(SQLiteDatabase db, @@ -550,6 +611,8 @@ public class BrowserProvider extends ContentProvider { if (guid.equals(Bookmarks.PLACES_FOLDER_GUID)) values.put(Bookmarks._ID, Bookmarks.FIXED_ROOT_ID); + else if (guid.equals(Bookmarks.READING_LIST_FOLDER_GUID)) + values.put(Bookmarks._ID, Bookmarks.FIXED_READING_LIST_ID); // Set the parent to 0, which sync assumes is the root values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID); @@ -845,6 +908,16 @@ public class BrowserProvider extends ContentProvider { db.execSQL("DROP TABLE " + TABLE_DUPES); } + private void upgradeDatabaseFrom8to9(SQLiteDatabase db) { + createOrUpdateSpecialFolder(db, Bookmarks.READING_LIST_FOLDER_GUID, + R.string.bookmarks_folder_reading_list, 5); + + debug("Dropping view: " + VIEW_COMBINED_WITH_IMAGES); + db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_IMAGES); + + createCombinedWithImagesViewOn9(db); + } + @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { debug("Upgrading browser.db: " + db.getPath() + " from " + @@ -882,6 +955,9 @@ public class BrowserProvider extends ContentProvider { case 8: upgradeDatabaseFrom7to8(db); + + case 9: + upgradeDatabaseFrom8to9(db); } } diff --git a/mobile/android/base/locales/en-US/sync_strings.dtd b/mobile/android/base/locales/en-US/sync_strings.dtd index 24e21cf4c5cf..ee1c3ef00ef4 100644 --- a/mobile/android/base/locales/en-US/sync_strings.dtd +++ b/mobile/android/base/locales/en-US/sync_strings.dtd @@ -72,6 +72,7 @@ + diff --git a/mobile/android/base/tests/testBrowserProvider.java.in b/mobile/android/base/tests/testBrowserProvider.java.in index c7aadea6a464..5e97744d4c1a 100644 --- a/mobile/android/base/tests/testBrowserProvider.java.in +++ b/mobile/android/base/tests/testBrowserProvider.java.in @@ -32,6 +32,7 @@ public class testBrowserProvider extends ContentProviderTest { private String TAGS_FOLDER_GUID; private String TOOLBAR_FOLDER_GUID; private String UNFILED_FOLDER_GUID; + private String READING_LIST_FOLDER_GUID; private Uri mBookmarksUri; private Uri mBookmarksPositionUri; @@ -77,12 +78,15 @@ public class testBrowserProvider extends ContentProviderTest { private String mCombinedIdCol; private String mCombinedBookmarkIdCol; private String mCombinedHistoryIdCol; + private String mCombinedDisplayCol; private String mCombinedUrlCol; private String mCombinedTitleCol; private String mCombinedVisitsCol; private String mCombinedLastVisitedCol; private String mCombinedFaviconCol; private String mCombinedThumbnailCol; + private int mCombinedDisplayNormal; + private int mCombinedDisplayReader; private void loadContractInfo() throws Exception { mBookmarksUri = getContentUri("Bookmarks"); @@ -98,6 +102,7 @@ public class testBrowserProvider extends ContentProviderTest { TAGS_FOLDER_GUID = getStringColumn("Bookmarks", "TAGS_FOLDER_GUID"); TOOLBAR_FOLDER_GUID = getStringColumn("Bookmarks", "TOOLBAR_FOLDER_GUID"); UNFILED_FOLDER_GUID = getStringColumn("Bookmarks", "UNFILED_FOLDER_GUID"); + READING_LIST_FOLDER_GUID = getStringColumn("Bookmarks", "READING_LIST_FOLDER_GUID"); mBookmarksIdCol = getStringColumn("Bookmarks", "_ID"); mBookmarksTitleCol = getStringColumn("Bookmarks", "TITLE"); @@ -137,12 +142,16 @@ public class testBrowserProvider extends ContentProviderTest { mCombinedIdCol = getStringColumn("Combined", "_ID"); mCombinedBookmarkIdCol = getStringColumn("Combined", "BOOKMARK_ID"); mCombinedHistoryIdCol = getStringColumn("Combined", "HISTORY_ID"); + mCombinedDisplayCol = getStringColumn("Combined", "DISPLAY"); mCombinedUrlCol = getStringColumn("Combined", "URL"); mCombinedTitleCol = getStringColumn("Combined", "TITLE"); mCombinedVisitsCol = getStringColumn("Combined", "VISITS"); mCombinedLastVisitedCol = getStringColumn("Combined", "DATE_LAST_VISITED"); mCombinedFaviconCol = getStringColumn("Combined", "FAVICON"); mCombinedThumbnailCol = getStringColumn("Combined", "THUMBNAIL"); + + mCombinedDisplayNormal = getIntColumn("Combined", "DISPLAY_NORMAL"); + mCombinedDisplayReader = getIntColumn("Combined", "DISPLAY_READER"); } private void loadMobileFolderId() throws Exception { @@ -163,16 +172,18 @@ public class testBrowserProvider extends ContentProviderTest { guid + " != ? AND " + guid + " != ? AND " + guid + " != ? AND " + + guid + " != ? AND " + guid + " != ?", new String[] { PLACES_FOLDER_GUID, MOBILE_FOLDER_GUID, MENU_FOLDER_GUID, TAGS_FOLDER_GUID, TOOLBAR_FOLDER_GUID, - UNFILED_FOLDER_GUID}); + UNFILED_FOLDER_GUID, + READING_LIST_FOLDER_GUID }); c = mProvider.query(appendUriParam(mBookmarksUri, "PARAM_SHOW_DELETED", "1"), null, null, null, null); - mAsserter.is(c.getCount(), 6, "All non-special bookmarks and folders were deleted"); + mAsserter.is(c.getCount(), 7, "All non-special bookmarks and folders were deleted"); mProvider.delete(appendUriParam(mHistoryUri, "PARAM_IS_SYNC", "1"), null, null); c = mProvider.query(appendUriParam(mHistoryUri, "PARAM_SHOW_DELETED", "1"), null, null, null, null); @@ -304,6 +315,7 @@ public class testBrowserProvider extends ContentProviderTest { mTests.add(new TestBatchOperations()); mTests.add(new TestCombinedView()); + mTests.add(new TestCombinedViewDisplay()); } public void testBrowserProvider() throws Exception { @@ -522,18 +534,21 @@ public class testBrowserProvider extends ContentProviderTest { mBookmarksGuidCol + " = ? OR " + mBookmarksGuidCol + " = ? OR " + mBookmarksGuidCol + " = ? OR " + + mBookmarksGuidCol + " = ? OR " + mBookmarksGuidCol + " = ?", new String[] { PLACES_FOLDER_GUID, MOBILE_FOLDER_GUID, MENU_FOLDER_GUID, TAGS_FOLDER_GUID, TOOLBAR_FOLDER_GUID, - UNFILED_FOLDER_GUID }, + UNFILED_FOLDER_GUID, + READING_LIST_FOLDER_GUID }, null); - mAsserter.is(c.getCount(), 6, "Right number of special folders"); + mAsserter.is(c.getCount(), 7, "Right number of special folders"); int rootId = getIntColumn("Bookmarks", "FIXED_ROOT_ID"); + int readingListId = getIntColumn("Bookmarks", "FIXED_READING_LIST_ID"); while (c.moveToNext()) { int id = c.getInt(c.getColumnIndex(mBookmarksIdCol)); @@ -542,6 +557,8 @@ public class testBrowserProvider extends ContentProviderTest { if (guid.equals(PLACES_FOLDER_GUID)) { mAsserter.is(new Integer(id), new Integer(rootId), "The id of places folder is correct"); + } else if (guid.equals(READING_LIST_FOLDER_GUID)) { + mAsserter.is(new Integer(id), new Integer(readingListId), "The id of reading list folder is correct"); } mAsserter.is(new Integer(parentId), new Integer(rootId), @@ -1416,4 +1433,59 @@ public class testBrowserProvider extends ContentProviderTest { BOOKMARK_THUMBNAIL, "Combined entry has bookmark thumbnail image"); } } + + class TestCombinedViewDisplay extends Test { + public void test() throws Exception { + final String TITLE_1 = "Test Page 1"; + final String TITLE_2 = "Test Page 2"; + final String TITLE_3_HISTORY = "Test Page 3 (History Entry)"; + final String TITLE_3_BOOKMARK = "Test Page 3 (Bookmark Entry)"; + final String TITLE_4 = "Test Page 4"; + + final String URL_1 = "http://example.com"; + final String URL_2 = "http://example.org"; + final String URL_3 = "http://examples2.com"; + final String URL_4 = "http://readinglist.com"; + + final int VISITS = 10; + final long LAST_VISITED = System.currentTimeMillis(); + + // Create a basic history entry + ContentValues basicHistory = createHistoryEntry(TITLE_1, URL_1, VISITS, LAST_VISITED); + ContentUris.parseId(mProvider.insert(mHistoryUri, basicHistory)); + + // Create a basic bookmark entry + ContentValues basicBookmark = createBookmark(TITLE_2, URL_2, mMobileFolderId, + mBookmarksTypeBookmark, 0, "tags", "description", "keyword"); + mProvider.insert(mBookmarksUri, basicBookmark); + + // Create a history entry and bookmark entry with the same URL to + // represent a visited bookmark + ContentValues combinedHistory = createHistoryEntry(TITLE_3_HISTORY, URL_3, VISITS, LAST_VISITED); + mProvider.insert(mHistoryUri, combinedHistory); + + ContentValues combinedBookmark = createBookmark(TITLE_3_BOOKMARK, URL_3, mMobileFolderId, + mBookmarksTypeBookmark, 0, "tags", "description", "keyword"); + mProvider.insert(mBookmarksUri, combinedBookmark); + + // Create a reading list entry + int readingListId = getIntColumn("Bookmarks", "FIXED_READING_LIST_ID"); + ContentValues readingListItem = createBookmark(TITLE_4, URL_4, readingListId, + mBookmarksTypeBookmark, 0, "tags", "description", "keyword"); + long readingListItemId = ContentUris.parseId(mProvider.insert(mBookmarksUri, readingListItem)); + + Cursor c = mProvider.query(mCombinedUri, null, "", null, null); + mAsserter.is(c.getCount(), 4, "4 combined entries found"); + + while (c.moveToNext()) { + long id = c.getLong(c.getColumnIndex(mCombinedBookmarkIdCol)); + + int display = c.getInt(c.getColumnIndex(mCombinedDisplayCol)); + int expectedDisplay = (id == readingListItemId ? mCombinedDisplayReader : mCombinedDisplayNormal); + + mAsserter.is(new Integer(display), new Integer(expectedDisplay), + "Combined display column should always be DISPLAY_READER for the reading list item"); + } + } + } } diff --git a/mobile/android/sync/strings.xml.in b/mobile/android/sync/strings.xml.in index 557e12d0cc1f..8563aa3e0218 100644 --- a/mobile/android/sync/strings.xml.in +++ b/mobile/android/sync/strings.xml.in @@ -64,6 +64,7 @@ &bookmarks.folder.unfiled.label; &bookmarks.folder.desktop.label; &bookmarks.folder.mobile.label; + &bookmarks.folder.readinglist.label; &sync.notification.oneaccount.label; From 24c5b4ee5cda7829a505fe44effeb68bdef62d70 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Sat, 2 Jun 2012 14:23:45 -0400 Subject: [PATCH 07/14] Bug 750684 - Show Reading List folder in the awesomebar screen (r=margaret) --- mobile/android/base/AwesomeBar.java | 16 ++- mobile/android/base/AwesomeBarTabs.java | 43 ++++++- mobile/android/base/Makefile.in | 3 + mobile/android/base/db/LocalBrowserDB.java | 105 ++++++++++++++---- .../base/locales/en-US/android_strings.dtd | 2 + .../resources/drawable-hdpi/reading_list.png | Bin 0 -> 2141 bytes .../drawable-xhdpi-v11/reading_list.png | Bin 0 -> 2475 bytes .../base/resources/drawable/reading_list.png | Bin 0 -> 1763 bytes mobile/android/base/strings.xml.in | 2 + 9 files changed, 145 insertions(+), 26 deletions(-) create mode 100644 mobile/android/base/resources/drawable-hdpi/reading_list.png create mode 100644 mobile/android/base/resources/drawable-xhdpi-v11/reading_list.png create mode 100644 mobile/android/base/resources/drawable/reading_list.png diff --git a/mobile/android/base/AwesomeBar.java b/mobile/android/base/AwesomeBar.java index 2842dba1a296..9c4a24b51fcc 100644 --- a/mobile/android/base/AwesomeBar.java +++ b/mobile/android/base/AwesomeBar.java @@ -15,6 +15,7 @@ import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.os.AsyncTask; import android.os.Bundle; import android.text.Editable; import android.text.Spanned; @@ -569,7 +570,14 @@ public class AwesomeBar extends GeckoActivity implements GeckoEventListener { break; } case R.id.remove_bookmark: { - (new GeckoAsyncTask() { + (new AsyncTask() { + private boolean mInReadingList; + + @Override + public void onPreExecute() { + mInReadingList = mAwesomeTabs.isInReadingList(); + } + @Override public Void doInBackground(Void... params) { BrowserDB.removeBookmark(mResolver, id); @@ -578,7 +586,11 @@ public class AwesomeBar extends GeckoActivity implements GeckoEventListener { @Override public void onPostExecute(Void result) { - Toast.makeText(AwesomeBar.this, R.string.bookmark_removed, Toast.LENGTH_SHORT).show(); + int messageId = R.string.bookmark_removed; + if (mInReadingList) + messageId = R.string.reading_list_removed; + + Toast.makeText(AwesomeBar.this, messageId, Toast.LENGTH_SHORT).show(); } }).execute(); break; diff --git a/mobile/android/base/AwesomeBarTabs.java b/mobile/android/base/AwesomeBarTabs.java index 3f2b407a6bef..faaf8ece0c88 100644 --- a/mobile/android/base/AwesomeBarTabs.java +++ b/mobile/android/base/AwesomeBarTabs.java @@ -78,6 +78,8 @@ public class AwesomeBarTabs extends TabHost { private BookmarksListAdapter mBookmarksAdapter; private SimpleExpandableListAdapter mHistoryAdapter; + private boolean mInReadingList; + // FIXME: This value should probably come from a // prefs key (just like XUL-based fennec) private static final int MAX_RESULTS = 100; @@ -184,6 +186,8 @@ public class AwesomeBarTabs extends TabHost { mBookmarksQueryTask.cancel(false); Pair folderPair = mParentStack.getFirst(); + mInReadingList = (folderPair.first == Bookmarks.FIXED_READING_LIST_ID); + mBookmarksQueryTask = new BookmarksQueryTask(folderPair.first, folderPair.second); mBookmarksQueryTask.execute(); } @@ -241,6 +245,8 @@ public class AwesomeBarTabs extends TabHost { return mResources.getString(R.string.bookmarks_folder_toolbar); else if (guid.equals(Bookmarks.UNFILED_FOLDER_GUID)) return mResources.getString(R.string.bookmarks_folder_unfiled); + else if (guid.equals(Bookmarks.READING_LIST_FOLDER_GUID)) + return mResources.getString(R.string.bookmarks_folder_reading_list); // If for some reason we have a folder with a special GUID, but it's not one of // the special folders we expect in the UI, just return the title from the DB. @@ -279,6 +285,15 @@ public class AwesomeBarTabs extends TabHost { updateUrl(viewHolder.urlView, cursor); updateFavicon(viewHolder.faviconView, cursor); } else { + int guidIndex = cursor.getColumnIndexOrThrow(Bookmarks.GUID); + String guid = cursor.getString(guidIndex); + + if (guid.equals(Bookmarks.READING_LIST_FOLDER_GUID)) { + viewHolder.faviconView.setImageResource(R.drawable.reading_list); + } else { + viewHolder.faviconView.setImageResource(R.drawable.folder); + } + viewHolder.titleView.setText(getFolderTitle(position)); } @@ -466,6 +481,7 @@ public class AwesomeBarTabs extends TabHost { byte[] favicon = cursor.getBlob(cursor.getColumnIndexOrThrow(URLColumns.FAVICON)); Integer bookmarkId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID)); Integer historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID)); + Integer display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY)); // Use the URL instead of an empty title for consistency with the normal URL // bar view - this is the equivalent of getDisplayTitle() in Tab.java @@ -719,6 +735,8 @@ public class AwesomeBarTabs extends TabHost { mContentResolver = context.getContentResolver(); mContentObserver = null; mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + mInReadingList = false; } @Override @@ -860,6 +878,12 @@ public class AwesomeBarTabs extends TabHost { return imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } + private String getReaderForUrl(String url) { + // FIXME: still need to define the final way to open items from + // reading list. For now, we're using an about:reader page. + return "about:reader?url=" + url; + } + private void handleBookmarkItemClick(AdapterView parent, View view, int position, long id) { int headerCount = ((ListView) parent).getHeaderViewsCount(); // If we tap on the header view, there's nothing to do @@ -885,8 +909,13 @@ public class AwesomeBarTabs extends TabHost { // Otherwise, just open the URL String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)); - if (mUrlOpenListener != null) + if (mUrlOpenListener != null) { + if (mInReadingList) { + url = getReaderForUrl(url); + } + mUrlOpenListener.onUrlOpen(url); + } } private void handleHistoryItemClick(int groupPosition, int childPosition) { @@ -908,8 +937,14 @@ public class AwesomeBarTabs extends TabHost { if (item instanceof Cursor) { Cursor cursor = (Cursor) item; String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)); - if (mUrlOpenListener != null) + if (mUrlOpenListener != null) { + int display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY)); + if (display == Combined.DISPLAY_READER) { + url = getReaderForUrl(url); + } + mUrlOpenListener.onUrlOpen(url); + } } else { if (mUrlOpenListener != null) mUrlOpenListener.onSearch((String)item); @@ -1003,6 +1038,10 @@ public class AwesomeBarTabs extends TabHost { }); } + public boolean isInReadingList() { + return mInReadingList; + } + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { hideSoftInput(this); diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index f21b3b72b861..df73e628609f 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -406,6 +406,7 @@ RES_DRAWABLE_BASE = \ res/drawable/site_security_identified.png \ res/drawable/site_security_verified.png \ res/drawable/urlbar_stop.png \ + res/drawable/reading_list.png \ res/drawable/validation_arrow.png \ res/drawable/validation_arrow_inverted.png \ res/drawable/validation_bg.9.png \ @@ -472,6 +473,7 @@ RES_DRAWABLE_HDPI = \ res/drawable-hdpi/site_security_identified.png \ res/drawable-hdpi/site_security_verified.png \ res/drawable-hdpi/urlbar_stop.png \ + res/drawable-hdpi/reading_list.png \ res/drawable-hdpi/validation_arrow.png \ res/drawable-hdpi/validation_arrow_inverted.png \ res/drawable-hdpi/validation_bg.9.png \ @@ -571,6 +573,7 @@ RES_DRAWABLE_XHDPI_V11 = \ res/drawable-xhdpi-v11/find_next.png \ res/drawable-xhdpi-v11/find_prev.png \ res/drawable-xhdpi-v11/urlbar_stop.png \ + res/drawable-xhdpi-v11/reading_list.png \ res/drawable-xhdpi-v11/larry_blue.png \ res/drawable-xhdpi-v11/larry_green.png \ res/drawable-xhdpi-v11/menu.png \ diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index ed30263cdeaf..eae04623814d 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -47,6 +47,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { // Use wrapped Boolean so that we can have a null state private Boolean mDesktopBookmarksExist; + private Boolean mReadingListItemsExist; private final Uri mBookmarksUriWithProfile; private final Uri mParentsUriWithProfile; @@ -70,6 +71,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { mProfile = profile; mFolderIdMap = new HashMap(); mDesktopBookmarksExist = null; + mReadingListItemsExist = null; mBookmarksUriWithProfile = appendProfile(Bookmarks.CONTENT_URI); mParentsUriWithProfile = appendProfile(Bookmarks.PARENTS_CONTENT_URI); @@ -88,6 +90,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { // Invalidate cached data public void invalidateCachedState() { mDesktopBookmarksExist = null; + mReadingListItemsExist = null; } private Uri historyUriWithLimit(int limit) { @@ -156,6 +159,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { Combined.URL, Combined.TITLE, Combined.FAVICON, + Combined.DISPLAY, Combined.BOOKMARK_ID, Combined.HISTORY_ID }, constraint, @@ -271,6 +275,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) { Cursor c = null; boolean addDesktopFolder = false; + boolean addReadingListFolder = false; // We always want to show mobile bookmarks in the root view. if (folderId == Bookmarks.FIXED_ROOT_ID) { @@ -279,6 +284,10 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { // We'll add a fake "Desktop Bookmarks" folder to the root view if desktop // bookmarks exist, so that the user can still access non-mobile bookmarks. addDesktopFolder = desktopBookmarksExist(cr); + + // We'll add the Reading List folder to the root view if any reading + // list items exist. + addReadingListFolder = readingListItemsExist(cr); } if (folderId == Bookmarks.FAKE_DESKTOP_FOLDER_ID) { @@ -306,9 +315,9 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { null); } - if (addDesktopFolder) { - // Wrap cursor to add fake desktop bookmarks folder - c = new DesktopBookmarksCursorWrapper(c); + if (addDesktopFolder || addReadingListFolder) { + // Wrap cursor to add fake desktop bookmarks and reading list folders + c = new SpecialFoldersCursorWrapper(c, addDesktopFolder, addReadingListFolder); } return new LocalDBCursor(c); @@ -340,10 +349,32 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { } // Cache result for future queries - mDesktopBookmarksExist = (count == 1); + mDesktopBookmarksExist = (count > 0); return mDesktopBookmarksExist; } + private boolean readingListItemsExist(ContentResolver cr) { + if (mReadingListItemsExist != null) + return mReadingListItemsExist; + + Cursor c = null; + int count = 0; + try { + c = cr.query(bookmarksUriWithLimit(1), + new String[] { Bookmarks._ID }, + Bookmarks.PARENT + " = ?", + new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) }, + null); + count = c.getCount(); + } finally { + c.close(); + } + + // Cache result for future queries + mReadingListItemsExist = (count > 0); + return mReadingListItemsExist; + } + public boolean isBookmark(ContentResolver cr, String uri) { Cursor cursor = cr.query(mBookmarksUriWithProfile, new String[] { Bookmarks._ID }, @@ -587,47 +618,72 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { // This wrapper adds a fake "Desktop Bookmarks" folder entry to the // beginning of the cursor's data set. - private static class DesktopBookmarksCursorWrapper extends CursorWrapper { - private boolean mAtDesktopBookmarksPosition = false; + private class SpecialFoldersCursorWrapper extends CursorWrapper { + private int mIndexOffset; - public DesktopBookmarksCursorWrapper(Cursor c) { + private int mDesktopBookmarksIndex = -1; + private int mReadingListIndex = -1; + + private boolean mAtDesktopBookmarksPosition = false; + private boolean mAtReadingListPosition = false; + + public SpecialFoldersCursorWrapper(Cursor c, boolean showDesktopBookmarks, boolean showReadingList) { super(c); + + mIndexOffset = 0; + + if (showDesktopBookmarks) { + mDesktopBookmarksIndex = mIndexOffset; + mIndexOffset++; + } + + if (showReadingList) { + mReadingListIndex = mIndexOffset; + mIndexOffset++; + } } @Override public int getCount() { - return super.getCount() + 1; + return super.getCount() + mIndexOffset; } @Override public boolean moveToPosition(int position) { - if (position == 0) { - mAtDesktopBookmarksPosition = true; - return true; - } + mAtDesktopBookmarksPosition = (mDesktopBookmarksIndex == position); + mAtReadingListPosition = (mReadingListIndex == position); - mAtDesktopBookmarksPosition = false; - return super.moveToPosition(position - 1); + if (mAtDesktopBookmarksPosition || mAtReadingListPosition) + return true; + + return super.moveToPosition(position - mIndexOffset); } @Override public long getLong(int columnIndex) { - if (!mAtDesktopBookmarksPosition) + if (!mAtDesktopBookmarksPosition && !mAtReadingListPosition) return super.getLong(columnIndex); - if (columnIndex == getColumnIndex(Bookmarks._ID)) - return Bookmarks.FAKE_DESKTOP_FOLDER_ID; - if (columnIndex == getColumnIndex(Bookmarks.PARENT)) + if (columnIndex == getColumnIndex(Bookmarks.PARENT)) { return Bookmarks.FIXED_ROOT_ID; + } return -1; } @Override public int getInt(int columnIndex) { - if (!mAtDesktopBookmarksPosition) + if (!mAtDesktopBookmarksPosition && !mAtReadingListPosition) return super.getInt(columnIndex); + if (columnIndex == getColumnIndex(Bookmarks._ID)) { + if (mAtDesktopBookmarksPosition) { + return Bookmarks.FAKE_DESKTOP_FOLDER_ID; + } else if (mAtReadingListPosition) { + return Bookmarks.FIXED_READING_LIST_ID; + } + } + if (columnIndex == getColumnIndex(Bookmarks.TYPE)) return Bookmarks.TYPE_FOLDER; @@ -636,11 +692,16 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { @Override public String getString(int columnIndex) { - if (!mAtDesktopBookmarksPosition) + if (!mAtDesktopBookmarksPosition && !mAtReadingListPosition) return super.getString(columnIndex); - if (columnIndex == getColumnIndex(Bookmarks.GUID)) - return Bookmarks.FAKE_DESKTOP_FOLDER_GUID; + if (columnIndex == getColumnIndex(Bookmarks.GUID)) { + if (mAtDesktopBookmarksPosition) { + return Bookmarks.FAKE_DESKTOP_FOLDER_GUID; + } else if (mAtReadingListPosition) { + return Bookmarks.READING_LIST_FOLDER_GUID; + } + } return ""; } diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index 088aa6a1e00e..5a24883859b1 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -124,6 +124,8 @@ + + diff --git a/mobile/android/base/resources/drawable-hdpi/reading_list.png b/mobile/android/base/resources/drawable-hdpi/reading_list.png new file mode 100644 index 0000000000000000000000000000000000000000..f125b66ed0c691ea03cb2dccc1c8ab02b2d0fedb GIT binary patch literal 2141 zcmbVNdpwls9v|1FEH+1pc#Z67JLYDtCc+rAEaNg*LKrhICT3pEyfSP{wi%>stTstY zM;Djm-b$;BIBP}ap2VhA#-7TxLZW@A=$!NU^w~eo`+45?d7j_%`+k3y=lA=(hrB$# z(9zzkjY6SxTwRzPWX3CBEluR>uey+iOh&MC0PF)r!0}u$h;rmZ;UM5DXp`&0pmX5>4$H!yiiC9P+i6hw9*eE%ytucrOMv@?cxl)WsVzJ7= z1SLGNAQl!tB0$N=4Ts`jJ2c|y`w)b&Z1x9Ykz^%NNXl?hZY+*~#p8rRWnAyHB`^p4 zj~gG=miQ*bf;bK+f#SqG4T_{@RdI5%GrZDHb4?!i`}R~dxgs98YFB{RUBNQ{5=(aygIpLA`$AC6Y6W;jK`>eNGVZX(EE6qpjfaE((5O|kiy5J)ouxM1)|3r?b5gFFjcqA5zGlfM( zuVDHAPu6gVGdN{={8zcGijW#qE8JBinb--V7Iqd(>OORrOtO9BnD6CKBxwS9tf^Xw&Y$d{sUc=wFccR5D>F zxi(uDcJi~jS-pK41AiNNi@nk2xwCR;Ti3Sv{YOFz7hji;$htNb-<_d)TkWa3QuF(Q zBQALdxOi&U#_zW_<=p>*cXfIh;_ph*)in^;q;NOo6`c=B#uJzNI%CE9lR-r)`dSQH z^87@2ye&a`8@G5=8<3SNGCjQYineS?$*Ht#t=OMD9o+Ky((&4BsFDJ=jj}ciGZUWs z=KB*no4n@7gC-l_(u+2v*GJYCpdu2;-8~!a-S3(1Z+Y}Mg_nzCx1ncK+MWz77ylO4 zvOL^o;c7X!U@m=}9bR2Ax^!^yJX+tf55~!A6+J%1llWgW8~Npi+V+veKZxfz)35r> zDx*xR$OXR7RCUwyJlzEs!wAQc?CJK4`B@1IMa%j6 z>JCR3p0+BBsX0If`mE_&HBSZSgxM3y@ZVqGGLD|!knMgI)J4p6S!$u1md&vttDX+`hw@98E(P8@aM`SorZs$ZZ&Z4GU(KlG zav$ohZg#5qd5tdrf1tQV_n6*M;}^e*JXt{wqa8cdsL{3l=#l;u`5)=I#&myCohHLY zl00TQ{?uGXemOpGTruO;6GM>wiph8iFq_)f+T#HJ6?tdpa`um^=FQb8=Iq>(QRmA{ zz0F2jB$f{C!`pJsehF8kWNk%m{`oRDb0FWKu|NHG zze5x`_n@3SypR1oSTL^NXcZjUE%;4b=hnY%dKUO}hee0NAINPVxbA(Xd`sCH^}4iH zU9+jL9rfBVvX_2q2ebN)3KLDnwbV;?-=x-2E~Z77sc8Y9p6~G;C*6Dy^OMfj*J+g9 z#Vr+Cq4Fo^oMrC%<3k3|UbTeltxv74;_4W~5}!kf&f9OAn|K3y>zm#TStT2KuAfna zOz?6#jC4vH9v!EKU%zl6e@^#Ssl0wot4ZcXxNs%CCoi{d04`C!d_w ze6ypW;Xw0^EMtH;oYQ8g6NF%SC+Nw@=3tg%N>wVTVMHng2dDuzK t!8on>DwOftk%6V+B>w}?Gm|wop-x{}?y8PFF{J$AyE=O?D;Qzl{s|REa%=zq literal 0 HcmV?d00001 diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/reading_list.png b/mobile/android/base/resources/drawable-xhdpi-v11/reading_list.png new file mode 100644 index 0000000000000000000000000000000000000000..fe2a6705a7494b35ef96e818b1ae99e4e0069e1b GIT binary patch literal 2475 zcmbVOdpK0<8ec9^E~U*W%4He1F3ijt=3*Ez#xOESLb7dRZYIse%(&;6Ozn!DLeWXN zR4%Es_5(W)>_~9z3ct`-uv^cqu!pbnra)= z007XWxlw%J)p6-rsRF-Cc@aDCVLdTU*;D4h+T$*07Sq3Z#q}D}lshnSsKV zFvUEPlqVE`ON@+AVWgCVfIa;Z0$)U@e$A2*>cjqz z8{gHI_{NIZC?B>&7%67L_28H+gW=r$xuGRR*c+mkm~9$eafYkv6fZG6}QcxJ@#aD141V*&sx`e_s=-qtv}ljIl#Oj{0#E`7$lg6jT+zY|i&B zRa>c0DBEJJU_mKF_!d(N&zH(J@n3Kxy*5WP9`c6Ga`ZmE*SD+QXm^#1F$`Vot%?Zd zJk4H=U!3GpGug$g(ER2SHOIfmwg9_;EoV*_vlj&g`%mOd%8oOs?dMQW_sR48_cdwZP*swS6jn>-;KrgJ+@61a%2!^Xs5olWxMzor)H$Ms$`_M zO?}&y&Dp4wQwy>y{ayRT>0=4gWB$rcqsqI6Fdg>Y3wGDzpEgHo#5n<2z=8(J3Z5Ht zjjHcg3_q!x=SCQCZ`HX6Yz8*#>!m7X4ZOgr6}3McTEKE*Z71tR8Mf7 zZ@~Pm^L5(Y4J*QQ?H9&2cTeG;epHBVsiaLep~(v^@eOTfpUEkRDa|B@=^5??|En>( zZ4ayTL|EMPmEWpY$bQ5zYTe3+nd>}IaVj`7%WrsUh{K({`F5}tRaJDWwxKfEcz4dV z-y5~tug3O;za&%|I3;!j?}_FSL=qbl?ee^?6P-i7vxdRBuB-hK0C?Kt_=6x83Gj9T z@;VpCM>IjkKx5G`-pTfiJW$B+_$(R}0NQ=#^T zTTM?U4g4-oO*l7xvZYW_ORbZ9tS!)kkW|~FanIAM>C>bkTK5;x$h2AG89hQyLBdM; z{eU-5+!L}wftXYjZdG$>4}fu4|LY7Yq{QO^Lt}?o#g9=yUq<_Dud*2`#W#WBaM0fM zz!6K7+>+K<-lr5fqj@{^#)ga1#I@#fW?o->m6D^6hOBL`%RcWRYs z*`Q*9LT0l=qGVo@-_0H>vIf$B%pJHUX#6lL^0fH1swP|dbK`UAy85Wu`f&xYK< zKxDX%6p!S(0d9k?l1rh7ZmT%<=CAs&Cvfxnnhdi$RbJY^4c7%X04KCuUg_17l$)l~ z^vxSX=j)6vn)?@47i`xIsip6iA+&q2Ku7EidgsZl-KsJb2gh!6pv)YN`=RlNSo;fW zejAwDR8$|C_p{n)uQDNLs8tmmXQMz{0hrzq96v6GY!jgH(W|;X62pqVGIO%+u`=br zIwKV*?e>5sMpwg|S?$d|O|K3+IlJ=qjx#|z$B?h1)kYr5`p0-DOd}o`n9{Yb4XAqh zn;2y0W*_Q!Mkp_D9b23>a6=sGn@>$kHqQ$psq;DMSvhsqPF}3E9FUgXQNxkDMELdu zDNX2hw>&>|KF7EyL`V4_b9zBp`4!VXkNv!q-2b(gb$GzJR?9svF(5eb;GFl7((9&X zYivyu8yes&&S^xoAd<8+&UhQ0f{S4xNV?9QM~l>+5fHc+^{0wslMLReq&H3dIj$t=ZXWH^7j_xZSvhZZ~#s2(ZLiyn2C5 z?-y7%W%&J3tlzkpLmqp<|R=~A(RTtVwI-JX0Kb8|gTO$ufF#gj1Ol41nL4AuETl~E3dp@xOs9YoDny2m z^46&YSu`lZ4H(F#F#}KthCvR82D8{44(tyyAecdi$eR-Yvjl98fWZYPFA8bRph^=c z#F9x{oc?Bfx0U5r{cJXIwbNAjXWSL8B)$ zxDK>3BB^*bA*7I=z70XEm&x7{>x`3$B2z{;BYHYagXmhVHLeM5BcZ_lapS$(Mx{lM z(G{2x&o-cBJ<=9Vfyvx`*pO9`^hOY6(2zwzGQ~KWt;KYNR4k;BUuY_gN&vw;GK*Xm z#AQGn9t`uiuq2GZ4C6{z5)p^Zp5k~PmMh{x;XEjmFBWrQI2;aTK@u^CA0Eb*uvik9 zIfa$#j0B=Xu_?P6((WXd|3R!kWWW#tHz;vDbE*TP)i{A0)wmuM#jM!PWIP8E(n^W{cgD5{UlVmMwN-k8qEnE9Degl2b`by`*_Ko+)jk z%JxQ5``6I-abyqN6MbD*r42v$Ew8J$%VL@v7k8kda6N5Xjcp38>!+*p8ar$Q@xt$` zrb(juI!C8@)@@0e9dUTOMzrmq>2}T2dmGRdFJ6w{R;ty!5__>Q{43_+)GXt}``vYW z9?qCkcgEF*dZ#e2Ec8pz;|_n7&zn(dbH^WUUc2S{_@8aUFF|q2_E_}sAt$-4DC*at zXZ__aOTHU){kmh>^=rYUx8hv-dbv!~?+wXr@|?xT(-hsZnH_gG9d$KUb?O|TpuExc zlJY&PtCb3kAOFo?PsV+H9FxCI$sV;h*q_^EUL!gl(%=nbotr}sa@yZ56g+sD7|Al7 z&3omS34Np>p$>_S_X{(tta~oO-Q5{o*2KA;j^oM#EclUXI zdOl>j%hr-20{?hj>VQr6$-e5QR@)Ls*TwC?=3{1CX&U=fulP^_xUOryT@e-%@%o6W zp=zUQg*~%5!~5qWsTG%tPQ=d|wpbjF`ES1}Sd=|n>9%}Sf5K%e+M=67WaBo`4KZ8m zi)4lO)lSBu*80;sa+@1xm31#qz}<3Gg{>p!k2dZ;om`cVUVp9t*5`ZM zZKy-tTmnlshaJNzE8U%e+N6l`A8UWSA_dw1z;U~LbvOmQ(auV?&9u#G)1R^6R`clgpO>u*OI LE*IB?r4;-P4H%t) literal 0 HcmV?d00001 diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index 98851b3d349e..a4d5381d7113 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -111,6 +111,8 @@ &site_settings_clear; &site_settings_no_settings; + &reading_list_removed; + &contextmenu_open_new_tab; &contextmenu_remove_history; &contextmenu_remove_bookmark; From 131ce038905cef17fc0a5ac151bd6c6270881c86 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Sat, 2 Jun 2012 14:23:45 -0400 Subject: [PATCH 08/14] Bug 750707 - Factor out method to add a bookmark, generalize folder GUID argument (r=margaret) --- mobile/android/base/db/LocalBrowserDB.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index eae04623814d..2b07c327c364 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -445,11 +445,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { debug("Updated " + updated + " rows to new modified time."); } - public void addBookmark(ContentResolver cr, String title, String uri) { - long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID); - if (folderId < 0) - return; - + private void addBookmarkItem(ContentResolver cr, String title, String uri, long folderId) { final long now = System.currentTimeMillis(); ContentValues values = new ContentValues(); values.put(Browser.BookmarkColumns.TITLE, title); @@ -463,8 +459,9 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { Uri contentUri = mBookmarksUriWithProfile; int updated = cr.update(contentUri, values, - Bookmarks.URL + " = ?", - new String[] { uri }); + Bookmarks.URL + " = ? AND " + + Bookmarks.PARENT + " = ?", + new String[] { uri, String.valueOf(folderId) }); if (updated == 0) cr.insert(contentUri, values); @@ -481,6 +478,11 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { debug("Updated " + updated + " rows to new modified time."); } + public void addBookmark(ContentResolver cr, String title, String uri) { + long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID); + addBookmarkItem(cr, title, uri, folderId); + } + public void removeBookmark(ContentResolver cr, int id) { Uri contentUri = mBookmarksUriWithProfile; From 07d7f23cf65e5a98618f8ef00faafaf23e07361e Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Sat, 2 Jun 2012 14:23:45 -0400 Subject: [PATCH 09/14] Bug 750707 - Add method to add reading list items in BrowserDB (r=margaret) --- mobile/android/base/db/AndroidBrowserDB.java | 4 ++++ mobile/android/base/db/BrowserDB.java | 6 ++++++ mobile/android/base/db/LocalBrowserDB.java | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/mobile/android/base/db/AndroidBrowserDB.java b/mobile/android/base/db/AndroidBrowserDB.java index f0ffb00f2843..1c15aa99dc6d 100644 --- a/mobile/android/base/db/AndroidBrowserDB.java +++ b/mobile/android/base/db/AndroidBrowserDB.java @@ -266,6 +266,10 @@ public class AndroidBrowserDB implements BrowserDB.BrowserDBIface { removeBookmarkPre11(cr, uri); } + public void addReadingListItem(ContentResolver cr, String title, String uri) { + // Do nothing + } + public void registerBookmarkObserverPre11(ContentResolver cr, ContentObserver observer) { cr.registerContentObserver(Browser.BOOKMARKS_URI, false, observer); } diff --git a/mobile/android/base/db/BrowserDB.java b/mobile/android/base/db/BrowserDB.java index bb713dfb54bb..9510da5e64a0 100644 --- a/mobile/android/base/db/BrowserDB.java +++ b/mobile/android/base/db/BrowserDB.java @@ -61,6 +61,8 @@ public class BrowserDB { public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword); + public void addReadingListItem(ContentResolver cr, String title, String uri); + public BitmapDrawable getFaviconForUrl(ContentResolver cr, String uri); public void updateFaviconForUrl(ContentResolver cr, String uri, BitmapDrawable favicon); @@ -148,6 +150,10 @@ public class BrowserDB { sDb.updateBookmark(cr, id, uri, title, keyword); } + public static void addReadingListItem(ContentResolver cr, String title, String uri) { + sDb.addReadingListItem(cr, title, uri); + } + public static BitmapDrawable getFaviconForUrl(ContentResolver cr, String uri) { return sDb.getFaviconForUrl(cr, uri); } diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 2b07c327c364..5945727629fc 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -506,6 +506,10 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { cr.delete(contentUri, urlEquals, urlArgs); } + public void addReadingListItem(ContentResolver cr, String title, String uri) { + addBookmarkItem(cr, title, uri, Bookmarks.FIXED_READING_LIST_ID); + } + public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) { cr.registerContentObserver(mBookmarksUriWithProfile, false, observer); } From 1d99a4b497131d6eea274bcb7ddc07c44029bf1a Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Sat, 2 Jun 2012 14:23:45 -0400 Subject: [PATCH 10/14] Bug 750707 - Toggling bookmarks off should only apply to normal bookmarks (r=margaret) --- mobile/android/base/db/LocalBrowserDB.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 5945727629fc..84aa1fef040c 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -501,8 +501,10 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { // Do this now so that the items still exist! bumpParents(cr, Bookmarks.URL, uri); - final String[] urlArgs = new String[] { uri }; - final String urlEquals = Bookmarks.URL + " = ?"; + // Toggling bookmark on an URL should not affect the items in the reading list + final String[] urlArgs = new String[] { uri, String.valueOf(Bookmarks.FIXED_READING_LIST_ID) }; + final String urlEquals = Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " != ?"; + cr.delete(contentUri, urlEquals, urlArgs); } From 1f86da7ef21967e560c1b425813ec4a3f7ca7ecf Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Sat, 2 Jun 2012 14:23:45 -0400 Subject: [PATCH 11/14] Bug 750707 - Remove unnecessary local variable in addBookmarkItem() (r=margaret) --- mobile/android/base/db/LocalBrowserDB.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 84aa1fef040c..4492758ebc11 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -456,15 +456,14 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { // Restore deleted record if possible values.put(Bookmarks.IS_DELETED, 0); - Uri contentUri = mBookmarksUriWithProfile; - int updated = cr.update(contentUri, + int updated = cr.update(mBookmarksUriWithProfile, values, Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " = ?", new String[] { uri, String.valueOf(folderId) }); if (updated == 0) - cr.insert(contentUri, values); + cr.insert(mBookmarksUriWithProfile, values); // Bump parent modified time using its ID. debug("Bumping parent modified time for addition to: " + folderId); @@ -474,7 +473,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { ContentValues bumped = new ContentValues(); bumped.put(Bookmarks.DATE_MODIFIED, now); - updated = cr.update(contentUri, bumped, where, args); + updated = cr.update(mBookmarksUriWithProfile, bumped, where, args); debug("Updated " + updated + " rows to new modified time."); } From 25fa318d2fb5acfbbd5a636255e84a76cd78de2f Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Sat, 2 Jun 2012 14:23:45 -0400 Subject: [PATCH 12/14] Bug 750707 - Check for bookmarks should only consider normal bookmarks (r=margaret) --- mobile/android/base/db/LocalBrowserDB.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 4492758ebc11..8dbeee389cfb 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -376,10 +376,13 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { } public boolean isBookmark(ContentResolver cr, String uri) { + // This method is about normal bookmarks, not the Reading List Cursor cursor = cr.query(mBookmarksUriWithProfile, new String[] { Bookmarks._ID }, - Bookmarks.URL + " = ?", - new String[] { uri }, + Bookmarks.URL + " = ? AND " + + Bookmarks.PARENT + " != ?", + new String[] { uri, + String.valueOf(Bookmarks.FIXED_READING_LIST_ID) }, Bookmarks.URL); int count = cursor.getCount(); From 673e2951b55e36b1695c026e7012b5398bc39e2f Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Sat, 2 Jun 2012 14:23:45 -0400 Subject: [PATCH 13/14] Bug 750707 - Pack URL bar buttons in same layout to allow showing multiple icons (r=mfinkle) --- mobile/android/base/BrowserToolbar.java | 2 +- .../layout-land-v14/browser_toolbar.xml | 38 ++++++++++--------- .../layout-sw600dp/browser_toolbar.xml | 38 ++++++++++--------- .../layout-xlarge/browser_toolbar.xml | 38 ++++++++++--------- .../base/resources/layout/browser_toolbar.xml | 38 ++++++++++--------- 5 files changed, 85 insertions(+), 69 deletions(-) diff --git a/mobile/android/base/BrowserToolbar.java b/mobile/android/base/BrowserToolbar.java index 2945ce50684d..2dc2cb2d4617 100644 --- a/mobile/android/base/BrowserToolbar.java +++ b/mobile/android/base/BrowserToolbar.java @@ -31,9 +31,9 @@ import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; import android.widget.PopupWindow; import android.widget.RelativeLayout; -import android.widget.RelativeLayout.LayoutParams; import android.widget.TextView; import android.widget.TextSwitcher; import android.widget.ViewSwitcher; diff --git a/mobile/android/base/resources/layout-land-v14/browser_toolbar.xml b/mobile/android/base/resources/layout-land-v14/browser_toolbar.xml index daa28582943a..459c79d716b5 100644 --- a/mobile/android/base/resources/layout-land-v14/browser_toolbar.xml +++ b/mobile/android/base/resources/layout-land-v14/browser_toolbar.xml @@ -86,24 +86,28 @@ android:src="@drawable/favicon" android:layout_alignLeft="@id/awesome_bar"/> - + - + + + + + - + - + + + + + - + - + + + + + - + - + + + + + Date: Sat, 2 Jun 2012 14:23:45 -0400 Subject: [PATCH 14/14] Bug 750707 - Add reader button to the browser toolbar (r=mfinkle) --- mobile/android/base/BrowserToolbar.java | 92 ++++++++++++++++-- mobile/android/base/GeckoApp.java | 23 +++++ mobile/android/base/Makefile.in | 4 + mobile/android/base/Tab.java | 32 ++++++ .../base/locales/en-US/android_strings.dtd | 3 + .../base/resources/drawable-hdpi/reader.png | Bin 0 -> 1633 bytes .../resources/drawable-xhdpi-v11/reader.png | Bin 0 -> 1945 bytes .../base/resources/drawable/reader.png | Bin 0 -> 1326 bytes .../base/resources/layout/browser_toolbar.xml | 7 ++ .../base/resources/layout/reader_popup.xml | 48 +++++++++ mobile/android/base/strings.xml.in | 3 + 11 files changed, 206 insertions(+), 6 deletions(-) create mode 100644 mobile/android/base/resources/drawable-hdpi/reader.png create mode 100644 mobile/android/base/resources/drawable-xhdpi-v11/reader.png create mode 100644 mobile/android/base/resources/drawable/reader.png create mode 100644 mobile/android/base/resources/layout/reader_popup.xml diff --git a/mobile/android/base/BrowserToolbar.java b/mobile/android/base/BrowserToolbar.java index 2dc2cb2d4617..ddf5313c22f1 100644 --- a/mobile/android/base/BrowserToolbar.java +++ b/mobile/android/base/BrowserToolbar.java @@ -12,6 +12,7 @@ import android.content.Context; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; @@ -24,18 +25,21 @@ import android.view.animation.TranslateAnimation; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; +import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewConfiguration; import android.view.Window; import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import android.widget.PopupWindow; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.TextSwitcher; +import android.widget.Toast; import android.widget.ViewSwitcher; public class BrowserToolbar implements ViewSwitcher.ViewFactory, @@ -49,6 +53,7 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory, public ImageButton mFavicon; public ImageButton mStop; public ImageButton mSiteSecurity; + public ImageButton mReader; private AnimationDrawable mProgressSpinner; private TextSwitcher mTabsCount; private ImageView mShadow; @@ -60,9 +65,13 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory, private LayoutInflater mInflater; private Handler mHandler; private int[] mPadding; - private boolean mTitleCanExpand; private boolean mHasSoftMenuButton; + private boolean mShowSiteSecurity; + private boolean mShowReader; + + private ReaderPopup mReaderPopup; + private static List sActionItems; private int mDuration; @@ -82,7 +91,10 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory, public void from(LinearLayout layout) { mLayout = layout; - mTitleCanExpand = true; + + mShowSiteSecurity = false; + mShowReader = false; + mReaderPopup = null; mAwesomeBar = (Button) mLayout.findViewById(R.id.awesome_bar); mAwesomeBar.setOnClickListener(new Button.OnClickListener() { @@ -152,6 +164,16 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory, } }); + mReader = (ImageButton) mLayout.findViewById(R.id.reader); + mReader.setOnClickListener(new Button.OnClickListener() { + public void onClick(View view) { + if (mReaderPopup == null) + mReaderPopup = new ReaderPopup(GeckoApp.mAppContext); + + mReaderPopup.show(); + } + }); + mShadow = (ImageView) mLayout.findViewById(R.id.shadow); mHandler = new Handler(); @@ -304,8 +326,11 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory, public void setStopVisibility(boolean visible) { mStop.setVisibility(visible ? View.VISIBLE : View.GONE); - mSiteSecurity.setVisibility(visible ? View.GONE : View.VISIBLE); - if (!visible && mTitleCanExpand) + + mSiteSecurity.setVisibility(mShowSiteSecurity && !visible ? View.VISIBLE : View.GONE); + mReader.setVisibility(mShowReader && !visible ? View.VISIBLE : View.GONE); + + if (!visible && !mShowSiteSecurity && !mShowReader) mAwesomeBar.setPadding(mPadding[0], mPadding[1], mPadding[2], mPadding[3]); else mAwesomeBar.setPadding(mPadding[0], mPadding[1], mPadding[0], mPadding[3]); @@ -342,7 +367,7 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory, } public void setSecurityMode(String mode) { - mTitleCanExpand = false; + mShowSiteSecurity = true; if (mode.equals(SiteIdentityPopup.IDENTIFIED)) { mSiteSecurity.setImageLevel(1); @@ -350,10 +375,14 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory, mSiteSecurity.setImageLevel(2); } else { mSiteSecurity.setImageLevel(0); - mTitleCanExpand = true; + mShowSiteSecurity = false; } } + public void setReaderVisibility(boolean showReader) { + mShowReader = showReader; + } + public void setVisibility(int visibility) { mLayout.setVisibility(visibility); } @@ -418,6 +447,7 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory, setTitle(tab.getDisplayTitle()); setFavicon(tab.getFavicon()); setSecurityMode(tab.getSecurityMode()); + setReaderVisibility(tab.getReaderEnabled()); setProgressVisibility(tab.getState() == Tab.STATE_LOADING); setShadowVisibility((url == null) || !url.startsWith("about:")); updateTabCount(Tabs.getInstance().getCount()); @@ -496,4 +526,54 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory, mArrow.setLayoutParams(newParams); } } + + public class ReaderPopup extends PopupWindow { + private int mWidth; + + private ReaderPopup(Context context) { + super(context); + + setBackgroundDrawable(new BitmapDrawable()); + setOutsideTouchable(true); + setWindowLayoutMode(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + + LayoutInflater inflater = LayoutInflater.from(context); + FrameLayout layout = (FrameLayout) inflater.inflate(R.layout.reader_popup, null); + setContentView(layout); + + layout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + mWidth = layout.getMeasuredWidth(); + + Button readingListButton = (Button) layout.findViewById(R.id.reading_list); + readingListButton.setOnClickListener(new Button.OnClickListener() { + public void onClick(View v) { + Tab selectedTab = Tabs.getInstance().getSelectedTab(); + if (selectedTab != null) { + selectedTab.addToReadingList(); + } + + Toast.makeText(GeckoApp.mAppContext, + R.string.reading_list_added, Toast.LENGTH_SHORT).show(); + + dismiss(); + } + }); + + Button readerModeButton = (Button) layout.findViewById(R.id.reader_mode); + readerModeButton.setOnClickListener(new Button.OnClickListener() { + public void onClick(View v) { + Tab selectedTab = Tabs.getInstance().getSelectedTab(); + if (selectedTab != null) { + selectedTab.readerMode(); + } + + dismiss(); + } + }); + } + + public void show() { + showAsDropDown(mReader, (mReader.getWidth() - mWidth) / 2, 0); + } + } } diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 48a7917c7554..59a54e90ec29 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -788,6 +788,7 @@ abstract public class GeckoApp tab.setContentType(contentType); tab.clearFavicon(); tab.updateIdentityData(null); + tab.setReaderEnabled(false); tab.removeTransientDoorHangers(); tab.setAllowZoom(true); tab.setDefaultZoom(0); @@ -827,6 +828,21 @@ abstract public class GeckoApp }); } + void handleReaderEnabled(final int tabId) { + final Tab tab = Tabs.getInstance().getTab(tabId); + if (tab == null) + return; + + tab.setReaderEnabled(true); + + mMainHandler.post(new Runnable() { + public void run() { + if (Tabs.getInstance().isSelectedTab(tab)) + mBrowserToolbar.setReaderVisibility(tab.getReaderEnabled()); + } + }); + } + void handleLoadError(final int tabId, final String uri, final String title) { final Tab tab = Tabs.getInstance().getTab(tabId); if (tab == null) @@ -1005,6 +1021,9 @@ abstract public class GeckoApp final JSONObject identity = message.getJSONObject("identity"); Log.i(LOGTAG, "Security Mode - " + identity.getString("mode")); handleSecurityChange(tabId, identity); + } else if (event.equals("Content:ReaderEnabled")) { + final int tabId = message.getInt("tabID"); + handleReaderEnabled(tabId); } else if (event.equals("Content:StateChange")) { final int tabId = message.getInt("tabID"); final String uri = message.getString("uri"); @@ -1363,12 +1382,14 @@ abstract public class GeckoApp tab.setState("about:home".equals(uri) ? Tab.STATE_SUCCESS : Tab.STATE_LOADING); tab.updateIdentityData(null); + tab.setReaderEnabled(false); if (Tabs.getInstance().isSelectedTab(tab)) getLayerController().getView().getRenderer().resetCheckerboard(); mMainHandler.post(new Runnable() { public void run() { if (Tabs.getInstance().isSelectedTab(tab)) { mBrowserToolbar.setSecurityMode(tab.getSecurityMode()); + mBrowserToolbar.setReaderVisibility(tab.getReaderEnabled()); mBrowserToolbar.updateBackButton(tab.canDoBack()); mBrowserToolbar.updateForwardButton(tab.canDoForward()); invalidateOptionsMenu(); @@ -1909,6 +1930,7 @@ abstract public class GeckoApp GeckoAppShell.registerGeckoEventListener("log", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("Content:LocationChange", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("Content:SecurityChange", GeckoApp.mAppContext); + GeckoAppShell.registerGeckoEventListener("Content:ReaderEnabled", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("Content:StateChange", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("Content:LoadError", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("Content:PageShow", GeckoApp.mAppContext); @@ -2260,6 +2282,7 @@ abstract public class GeckoApp GeckoAppShell.unregisterGeckoEventListener("log", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("Content:LocationChange", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("Content:SecurityChange", GeckoApp.mAppContext); + GeckoAppShell.unregisterGeckoEventListener("Content:ReaderEnabled", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("Content:StateChange", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("Content:LoadError", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("Content:PageShow", GeckoApp.mAppContext); diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index df73e628609f..955e5080b21c 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -272,6 +272,7 @@ RES_LAYOUT = \ res/layout/site_setting_title.xml \ res/layout/setup_screen.xml \ res/layout/site_identity_popup.xml \ + res/layout/reader_popup.xml \ res/layout/remote_tabs.xml \ res/layout/remote_tabs_child.xml \ res/layout/remote_tabs_group.xml \ @@ -406,6 +407,7 @@ RES_DRAWABLE_BASE = \ res/drawable/site_security_identified.png \ res/drawable/site_security_verified.png \ res/drawable/urlbar_stop.png \ + res/drawable/reader.png \ res/drawable/reading_list.png \ res/drawable/validation_arrow.png \ res/drawable/validation_arrow_inverted.png \ @@ -473,6 +475,7 @@ RES_DRAWABLE_HDPI = \ res/drawable-hdpi/site_security_identified.png \ res/drawable-hdpi/site_security_verified.png \ res/drawable-hdpi/urlbar_stop.png \ + res/drawable-hdpi/reader.png \ res/drawable-hdpi/reading_list.png \ res/drawable-hdpi/validation_arrow.png \ res/drawable-hdpi/validation_arrow_inverted.png \ @@ -573,6 +576,7 @@ RES_DRAWABLE_XHDPI_V11 = \ res/drawable-xhdpi-v11/find_next.png \ res/drawable-xhdpi-v11/find_prev.png \ res/drawable-xhdpi-v11/urlbar_stop.png \ + res/drawable-xhdpi-v11/reader.png \ res/drawable-xhdpi-v11/reading_list.png \ res/drawable-xhdpi-v11/larry_blue.png \ res/drawable-xhdpi-v11/larry_green.png \ diff --git a/mobile/android/base/Tab.java b/mobile/android/base/Tab.java index eae1f70a04a2..e7737c4ef765 100644 --- a/mobile/android/base/Tab.java +++ b/mobile/android/base/Tab.java @@ -43,6 +43,7 @@ public final class Tab { private String mFaviconUrl; private int mFaviconSize; private JSONObject mIdentityData; + private boolean mReaderEnabled; private Drawable mThumbnail; private int mHistoryIndex; private int mHistorySize; @@ -80,6 +81,7 @@ public final class Tab { mFaviconUrl = null; mFaviconSize = 0; mIdentityData = null; + mReaderEnabled = false; mThumbnail = null; mHistoryIndex = -1; mHistorySize = 0; @@ -199,6 +201,10 @@ public final class Tab { return mIdentityData; } + public boolean getReaderEnabled() { + return mReaderEnabled; + } + public boolean isBookmark() { return mBookmark; } @@ -333,6 +339,10 @@ public final class Tab { mIdentityData = identityData; } + public void setReaderEnabled(boolean readerEnabled) { + mReaderEnabled = readerEnabled; + } + private void updateBookmark() { final String url = getURL(); if (url == null) @@ -372,6 +382,28 @@ public final class Tab { }); } + public void addToReadingList() { + if (!mReaderEnabled) + return; + + GeckoAppShell.getHandler().post(new Runnable() { + public void run() { + String url = getURL(); + if (url == null) + return; + + BrowserDB.addReadingListItem(mContentResolver, getTitle(), url); + } + }); + } + + public void readerMode() { + if (!mReaderEnabled) + return; + + // Do nothing for now + } + public boolean doReload() { GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", ""); GeckoAppShell.sendEventToGecko(e); diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index 5a24883859b1..98dd4cb1783f 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -124,7 +124,10 @@ + + + diff --git a/mobile/android/base/resources/drawable-hdpi/reader.png b/mobile/android/base/resources/drawable-hdpi/reader.png new file mode 100644 index 0000000000000000000000000000000000000000..7cce977308e0d5dbc2c3fdf8045b96105b78f417 GIT binary patch literal 1633 zcmbVMeM}Q)7(c$mAWDU~z|e7?sIaxYkABgUTCOdHj#gR0Se7O9>!7#NUb!B$h)Q)+ z(5TGmCK^<9Y)f3`mf*LE5HX654HA}ZZp$1K#}J+IV-8oayJGA1hw+bH-n;j`_nzPH zdEVdiJh!EEMbYe}my!Sgn5`?;7`Zt+e9xT0J%@p|Hg3vb3r%bpUB&t=3<(f++Dd{t zm!+CCk`{YI?PW3_0OEF060V4JO;y4Vz)OU6ek(%w|HPwh`=sa_*~Q0Ue-wd8^$xO zz2yxa5;l@vx{k4N`KVeH1#{Xx-%;3*iw0lGP+U?hH5%Gh=OW##PNRUhFG4$I#}PD- zQ&A>HWEdjNL(x1Ls#Rekl}szvs-+T1l;c_0La{UtK{PVC22r7CAu5+@2$hCVV_2Sw zkYn;FR_FGz7PpOz`lUF(5v=sNSX|AJ7M5npX}Ts_0i_O_rM(W?1FFlUU=Au0BGZXS zM2JM_5{gktqz*(dBEc~fM-Xo0f?$rpVx!z)Y;HK0Po&Y13{_9swG8b7r;~wG&j^-K z%Ox@_uMj07BB@caoRAZU8p-3%sf8j~`~MLb=7NF4mGqy=if(b`8eTr_6Yk|{r;%>1 zy%?^K@@um`1b|q(PD7OYf3Qx5LdsrqtUmee07Xf`2Oe%-K_`AANU}H3_K$p-}J=n3L1<3ZFl6@xq19q}}ADL(Rii@9)`jWh--H*GR`F0|#lq zSkv7Uy7=-jy*|0UeSUI(e}Cmiix`@}C?oZE)7iD_7oRbE?1$su zW#TrgzI^}F?<55~V=lCAI5nfi7jt(OziJgwyR3q0;Q@H6eHiNKJrOgeE={}|cQt zs@`6}-?3!!J@*aX2jZbtLq00lGdwg3Kk)I6<^=x=b@v3`8FcL4uFd%DaI(obG(dFh z+q-}N?9XF1#>V!DAM72@LXu9OKAk=>;m?@xH)I47mc%i=H-fEM$#CVls)@Vzm&P4^ zF`h`Qt!1#{l9D(2+q%AJZ*I=_u7&}Q{ z3mmYWYHqU+sGnS1NxRIs(Jb7R*Zw(OIm zNzRm*+F863V$;F7j+BMfsW-@pJ6 Z2kebsaH!C4SR4N7>k3zBj;hQX{{fqyPgMW_ literal 0 HcmV?d00001 diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/reader.png b/mobile/android/base/resources/drawable-xhdpi-v11/reader.png new file mode 100644 index 0000000000000000000000000000000000000000..c4a323921f245e0fa831a63725c11fa3ff21e7c4 GIT binary patch literal 1945 zcmbVNX;c$e6ppB@T5SY(a0m#pWR{R!7PAlt8bBnnxrJnqh?$AW00FHCRsqEg<*0yK zv}}rqAQX2LIW9pEk5$CwD6}r8%2Ke3^=Kyuwm&@n=$tq6-n_Zrz4v|herK{ng9FA} zPq(H}DC4Dp;xKZCO}C{v`HV0d%p|92gnuNl2#+I@6*`n6P~ovCAk`>VqG6~)m9pkK z>P?}H-mMOgBqC)C_)1&@Doi$@L8B$n6pFW>L90+Epac+$u2f?_w4SpUX@FYgLyKU^ zV3}5g#;XHUb?Bnh;BaMXf|94A`S}9g20rOPgAxkBpjnOS`34`_kY7HzHf=*RV910> z@S%N9DpD2-h;SVWus|=m5=Ib!%?1%Bi_Jzn0S1gPAebC%I>O|$*n9>D7`|v^G@UAr zA10Oz$0FZ+Xz>K0(Au5W?f}OgBOb)4Ua3=S z2{n!ZCXb3(JdyCBk(quzf<`NoeQ}KGhlwI7gA58Sgn%%l(U{IPWUVK{(0|kTsxC_!`!q$>zeaSi%#-LIm+gcxDqq46abX zWAH|>QcO=MFeN$?S53wp#`=E~%NOZT1%d0raeVbi2ZYAs1g?+AwSZ_58<>N5f$&h_ zVK0~kYCtB!8twxx2xjpa2p@*Y;RXP6WD2DkGhy9Kwfszt7}cqhP?bc7Yk;9*@YP>Q zRv_ZBI1H{oA{Zt~gfMvmo&XlXT=JX}+Avo2e+Y)iWFS)~{im}=w#arhEx+i5eEFg@ z6eH!OBX#6D;gSO>&qAqK5N_yr6(x(GB(PmyleSQqKlwG5UpD~W@e7?;0R%$U(M^7# zeG^(827rtg0{>H4w09fRECg-t2j7&1ez+87lPXVm@W@$Kpt()d6jb1y5)YS`SS;c3 z_Y8ru{r7+5n}aUCm)L;;m=i{O?-nv2+H)&mac#CoN?#Uda#>MGTkp5 zeyX;v;+f$>`}U1F2UD`a+Qy@&Ken1hxuy8^I_}VQuZ`xtS!^=uDhX?|}mcGaeB+?exK#vtbjsXzZj-PdCwwRNmNu(!<*?>;j4F=dL~?EL1UmAO|WqYgcC>iv-9o#EM7 zX=B~`aP6)B0*7^=-?Lc->8;Dn`}etxrCEAM%o9%{?YtL}stHgST(T*{p4CueTi-mF-!zF%I#7Z2m#Mi(8KXOYzHg@S(?FvcR`QH@gnO5)r-X5 z%uK(Slwwt1_{~pdEh{I|bfHk73U10U2WTh=g3ZCQE^Oh-kLZ?Mbm{rl8iR!LimB!- z)zE>>D5s5K%TMA+*N&j&5{Y$UJzo_HOGX#v91SUk)-?NEtJZldiT({^L+gBMB!}oE z${WL`g7eYeS_5P4?(N7n#LwGXB0W54gKmmhO+4dfI7*{DkgL{#n`StX zg(Ab?u?K**gsiB#jooACa!ncund&nr6Eh7BtR_QLHwZQq5jZdFi^5P z7#}`ojI3fa|3_dN2SeMH^qA1a2+($1qmzxQq4#uTWs`y>H zva%9zxazLGQ-A!*_^A)4CM%Uvx+U;jdil=pyB!@JI~^_b?ZJojrG}Y5X6YmIy~qBp zJn`d=OGlg!-Mr}xet^g9&+i>5fAvNBZ2vC{7Y{FteLV`U)-|Q;({8Y|X|W|Z*EI3$ z*s?cu)iL3C|K!5>`NG@ZP}70UCjy+0z0)@F{G(qI+a4s(Z+hu79yD#eKIK?slGljY z`ru@5-Mf1akS8B$AzaQ~f7Wd$u5=t91A%kprQSD7GjE*!{MVl@xEI|!E?-{>n!lZ4 zou$&8v%S6DIXBlyd^E)cW0?`-&?l=mtQzoZg|}J zz{N24>8{4|>oc!8%TJCiH@rHvd)s6*I9(rXJ$5d8)c^EZ<_rNoA+~OQWpKj#ZQTBY N#iL!)>F`tI{{XpryIcSO literal 0 HcmV?d00001 diff --git a/mobile/android/base/resources/layout/browser_toolbar.xml b/mobile/android/base/resources/layout/browser_toolbar.xml index c4331e8b6d67..a7a14256353f 100644 --- a/mobile/android/base/resources/layout/browser_toolbar.xml +++ b/mobile/android/base/resources/layout/browser_toolbar.xml @@ -93,6 +93,13 @@ android:layout_alignRight="@id/awesome_bar" android:orientation="horizontal"> + + + + + + + + +