targetPageMeta.header.title (findMetaById(id).header.title)
* 3. innerText (to.meta.defaultTitle)
*/
let targetPageMeta = fn.extend(true, {}, this.findMetaByPageId(targetPageId))
- document.title = targetPageMeta.header.title = to.meta.title || targetPageMeta.header.title || to.meta.defaultTitle
+ this.targetPageTitle = to.meta.header
+ ? to.meta.header.title || targetPageMeta.header.title || to.meta.header.defaultTitle
+ : targetPageMeta.header.title
+ document.title = targetPageMeta.header.title = this.targetPageTitle
// Transition direction
let isForward
diff --git a/packages/mip/src/index.js b/packages/mip/src/index.js
index 084b0835..69f930b9 100644
--- a/packages/mip/src/index.js
+++ b/packages/mip/src/index.js
@@ -27,6 +27,7 @@ import MipShell from './components/mip-shell/index'
import registerCustomElement from './register-element'
import sleepWakeModule from './sleep-wake-module'
import performance from './performance'
+import templates from './util/templates'
import mip1PolyfillInstall from './mip1-polyfill/index'
import monitorInstall from './log/monitor'
@@ -101,6 +102,8 @@ let mip = {
sandbox,
css: {},
push,
+ performance,
+ templates,
prerenderElement: Resources.prerenderElement,
builtinComponents: {
// MipShell 应该删除,不符合命名
diff --git a/packages/mip/src/page/index.js b/packages/mip/src/page/index.js
index 43b0951a..8df70b2f 100644
--- a/packages/mip/src/page/index.js
+++ b/packages/mip/src/page/index.js
@@ -26,6 +26,7 @@ import {
import {customEmit} from '../util/custom-event'
import viewport from '../viewport'
+import performance from '../performance'
import '../styles/mip.less'
/**
@@ -65,7 +66,8 @@ class Page {
}
try {
- const anchor = document.getElementById(decodeURIComponent(hash.slice(1)))
+ const anchor = document.getElementById(hash.slice(1)) ||
+ document.getElementById(decodeURIComponent(hash.slice(1)))
/* istanbul ignore next */
if (anchor) {
@@ -170,8 +172,17 @@ class Page {
ensureMIPShell()
this.initPageId()
- // scroll to current hash if exists
- this.scrollToHash(window.location.hash)
+ /**
+ * scroll to anchor after all the elements loaded
+ * fix: https://github.com/mipengine/mip2/issues/125
+ */
+ performance.on('update', timing => {
+ if (timing.MIPFirstScreen) {
+ // scroll to current hash if exists
+ this.scrollToHash(window.location.hash)
+ }
+ })
+
window.addEventListener(CUSTOM_EVENT_SCROLL_TO_ANCHOR, (e) => {
this.scrollToHash(e.detail[0])
})
@@ -243,10 +254,12 @@ class Page {
customEmit(window, event.name, event.data)
this.children.forEach(pageMeta => {
- pageMeta.targetWindow.postMessage({
- type: MESSAGE_CROSS_ORIGIN,
- data: event
- }, '*')
+ if (pageMeta.targetWindow) {
+ pageMeta.targetWindow.postMessage({
+ type: MESSAGE_CROSS_ORIGIN,
+ data: event
+ }, '*')
+ }
})
} else {
window.parent.postMessage({
diff --git a/packages/mip/src/util/dom/rect.js b/packages/mip/src/util/dom/rect.js
index 469e6738..4e37dc8b 100644
--- a/packages/mip/src/util/dom/rect.js
+++ b/packages/mip/src/util/dom/rect.js
@@ -142,7 +142,10 @@ export default {
setScrollTop (top) {
if (setterElement) {
setterElement.style.top = top + 'px'
- setterElement.scrollIntoView(true)
+ // 如果页面头部是一个图片(或者其他后续会改变高度的元素)
+ // 直接执行 scrollIntoView 会导致页面一打开就滚动到半当中,而不是头部
+ // setTimeout 0 可以解决这个问题
+ setTimeout(() => setterElement.scrollIntoView(true), 0)
} else {
this.scrollingElement.scrollTop = top
}
diff --git a/packages/mip/src/viewer.js b/packages/mip/src/viewer.js
index 15713edd..155de640 100644
--- a/packages/mip/src/viewer.js
+++ b/packages/mip/src/viewer.js
@@ -405,14 +405,15 @@ let viewer = {
if (this.isIframed) {
this.lockBodyScroll()
- // Fix iphone 5s UC and ios 9 safari bug.
// While the back button is clicked,
// the cached page has some problems.
- // So we are forced to load the page in iphone 5s UC
- // and iOS 9 safari.
+ // So we are forced to load the page in below conditions:
+ // 1. IOS 8 + UC
+ // 2. IOS 9 & 10 + Safari
+ // 3. IOS 8 & 9 & 10 + UC & BaiduApp & Baidu
let needBackReload = (iosVersion === '8' && platform.isUc() && screen.width === 320) ||
- (iosVersion === '9' && platform.isSafari()) ||
- (iosVersion === '10' && platform.isSafari())
+ ((iosVersion === '9' || iosVersion === '10') && platform.isSafari()) ||
+ ((iosVersion === '8' || iosVersion === '9' || iosVersion === '10') && (platform.isUc() || platform.isBaiduApp() || platform.isBaidu()))
if (needBackReload) {
window.addEventListener('pageshow', e => {
if (e.persisted) {
diff --git a/packages/mip/src/vue-custom-element/utils/props.js b/packages/mip/src/vue-custom-element/utils/props.js
index 15303a96..4cf19bee 100644
--- a/packages/mip/src/vue-custom-element/utils/props.js
+++ b/packages/mip/src/vue-custom-element/utils/props.js
@@ -62,44 +62,50 @@ export function convertAttributeValue (value, type) {
* 解析 vue 组件的 props
*
* @see https://vuejs.org/v2/guide/components-props.html#Prop-Casing-camelCase-vs-kebab-case
- * @param {Array|Object} propsDef props collection
+ * @param {Object} component definition
* @param {Object} props extract props
*/
-function extractProps (propsDef, props) {
- if (isArray(propsDef)) {
- propsDef.forEach(prop => {
- let camelCaseProp = camelize(prop)
- props.camelCase.indexOf(camelCaseProp) === -1 && props.camelCase.push(camelCaseProp)
- props.types[prop] = getPropType(propsDef[camelCaseProp])
+function extractProps (def, propTypes) {
+ if (isArray(def.props)) {
+ def.props.forEach(prop => {
+ let camelizeName = camelize(prop)
+ if (!propTypes[camelizeName]) {
+ propTypes[camelizeName] = getPropType(def.props[prop])
+ }
})
- } else if (propsDef && typeof propsDef === 'object') {
- for (let prop in propsDef) {
- let camelCaseProp = camelize(prop)
- props.camelCase.indexOf(camelCaseProp) === -1 && props.camelCase.push(camelCaseProp)
- props.types[prop] = getPropType(propsDef[camelCaseProp])
+ } else if (typeof def.props === 'object') {
+ for (let prop in def.props) {
+ let camelizeName = camelize(prop)
+ if (!propTypes[camelizeName]) {
+ propTypes[camelizeName] = getPropType(def.props[prop])
+ }
}
}
+
+ if (def.extends && def.extends.props) {
+ extractProps(def.extends, propTypes)
+ }
+
+ if (def.mixins) {
+ def.mixins.forEach(mixin => extractProps(mixin, propTypes))
+ }
+
+ return propTypes
}
// Extract props from component definition, no matter if it's array or object
-export function getProps (componentDefinition = {}) {
+export function getProps (def = {}) {
let props = {
camelCase: [],
hyphenate: [],
types: {}
}
- if (componentDefinition.mixins) {
- componentDefinition.mixins.forEach(mixin => extractProps(mixin.props, props))
- }
-
- if (componentDefinition.extends && componentDefinition.extends.props) {
- extractProps(componentDefinition.extends.props, props)
- }
-
- extractProps(componentDefinition.props, props)
+ let propTypes = extractProps(def, {})
- props.camelCase.forEach(prop => props.hyphenate.push(hyphenate(prop)))
+ props.camelCase = Object.keys(propTypes)
+ props.hyphenate = Object.keys(propTypes).map(key => hyphenate(key))
+ props.types = propTypes
return props
}
diff --git a/packages/mip/test/e2e/cases/index.html b/packages/mip/test/e2e/cases/index.html
index 18d065d6..7162f243 100644
--- a/packages/mip/test/e2e/cases/index.html
+++ b/packages/mip/test/e2e/cases/index.html
@@ -35,6 +35,7 @@
Go to Tree
Go to API
page.forward()
+ Go to Scroll to Anchor
diff --git a/packages/mip/test/e2e/cases/scroll-to-anchor.html b/packages/mip/test/e2e/cases/scroll-to-anchor.html
new file mode 100644
index 00000000..d46328ce
--- /dev/null
+++ b/packages/mip/test/e2e/cases/scroll-to-anchor.html
@@ -0,0 +1,130 @@
+
+
+
+
+ MIP Index Page
+
+
+
+
+
+
+
+
+
+
+
+ Anchor2
+
+
+ Progressive Web Apps are user experiences that have the reach of the web, and are:
+Reliable - Load instantly and never show the downasaur, even in uncertain network conditions.
+Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling.
+
+
+ Go to Tree
+ Go to API
+ page.forward()
+
+
+
+ Anchor
+ Anchor3
+
+
+
+ Progressive Web Apps are user experiences that have the reach of the web, and are:
+Reliable - Load instantly and never show the downasaur, even in uncertain network conditions.
+Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling.
+
+
+ Progressive Web Apps are user experiences that have the reach of the web, and are:
+Reliable - Load instantly and never show the downasaur, even in uncertain network conditions.
+Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling.
+
+
+ Progressive Web Apps are user experiences that have the reach of the web, and are:
+Reliable - Load instantly and never show the downasaur, even in uncertain network conditions.
+Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling.
+
+
+
+ Progressive Web Apps are user experiences that have the reach of the web, and are:
+Reliable - Load instantly and never show the downasaur, even in uncertain network conditions.
+Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling.
+
+
+
+
+
+
+
+
+
diff --git a/packages/mip/test/e2e/specs/scroll-to-anchor.js b/packages/mip/test/e2e/specs/scroll-to-anchor.js
new file mode 100644
index 00000000..365d4cfe
--- /dev/null
+++ b/packages/mip/test/e2e/specs/scroll-to-anchor.js
@@ -0,0 +1,107 @@
+/**
+ * @file scroll to anchor e2e test case (standalone 模式)
+ * @author panyuqi
+ * @description 测试流程:
+ * 1. 打开 scroll-to-anchor.html
+ * 2. 点击 滚动到锚点处
+ * 3. 在当前页面刷新,滚动到锚点处
+ * 4. 打开 index.html 通过 mip-link 跳转到 scroll-to-anchor.html 验证 iframe 下的情况
+ * 5. 点击 iframe 中的 滚动到锚点处
+ *
+ * fix ISSUE: https://github.com/mipengine/mip2/issues/125
+ */
+
+let INDEX_PAGE_URL
+let SCROLL_PAGE_URL
+let positionY = 0 // anchor's position in Yaxis
+let positionY2 = 0
+let positionY3 = 0
+
+module.exports = {
+ 'open page': function (browser) {
+ INDEX_PAGE_URL = `${browser.globals.devServerURL}/test/e2e/cases/index.html`
+ SCROLL_PAGE_URL = `${browser.globals.devServerURL}/test/e2e/cases/scroll-to-anchor.html`
+
+ browser
+ // open index.html
+ .url(SCROLL_PAGE_URL)
+ // show mip-shell
+ .waitForElementVisible('body', 2000)
+ },
+ 'scroll to correct position when click an anchor': function (browser) {
+ const anchor1 = '#anchor'
+ const anchor2 = '#anchor-%E8%AE'
+ const anchor3 = '#anchor-设置轮播时间间隔'
+ const hashOfAnchor3 = '#anchor-%E8%AE%BE%E7%BD%AE%E8%BD%AE%E6%92%AD%E6%97%B6%E9%97%B4%E9%97%B4%E9%9A%94'
+
+ browser
+ // save anchor's position
+ .getLocation(anchor3, function (result) {
+ positionY2 = result.value.y
+ })
+ // click anchor
+ .waitForClick(anchor3)
+ .pause(1000)
+ // hash changed
+ .assert.urlContains(hashOfAnchor3)
+ .execute(function () {
+ // it should scroll to top
+ this.assert.equal(window.MIP.viewport.getScrollTop(), positionY3)
+ })
+ // save anchor's position
+ .getLocation('.encoded-anchor', function (result) {
+ positionY2 = result.value.y
+ })
+ // click anchor
+ .waitForClick('.encoded-anchor')
+ .pause(1000)
+ // hash changed
+ .assert.urlContains(anchor2)
+ .execute(function () {
+ // it should scroll to top
+ this.assert.equal(window.MIP.viewport.getScrollTop(), positionY2)
+ })
+ // save anchor's position
+ .getLocation(anchor1, function (result) {
+ positionY = result.value.y
+ })
+ // click anchor
+ .waitForClick(anchor1)
+ .pause(1000)
+ // hash changed
+ .assert.urlContains(anchor1)
+ .execute(function () {
+ // it should scroll to top
+ this.assert.equal(window.MIP.viewport.getScrollTop(), positionY)
+ })
+ },
+ 'scroll to correct position when refresh': function (browser) {
+ browser
+ .refresh()
+ // hash contains #anchor
+ .assert.urlContains('#anchor')
+ .pause(1000)
+ .execute(function () {
+ // it should scroll to top
+ this.assert.equal(window.MIP.viewport.getScrollTop(), positionY)
+ })
+ },
+ 'open scroll page in iframe': function (browser) {
+ browser
+ // open index.html
+ .url(INDEX_PAGE_URL)
+ // open tree.html
+ .waitForClick('.scroll-to-anchor-link')
+ .enterIframe(SCROLL_PAGE_URL, () => {
+ browser
+ // click anchor
+ .waitForClick('#anchor')
+ .pause(1000)
+ .execute(function () {
+ // it should scroll to top
+ this.assert.equal(window.MIP.viewport.getScrollTop(), positionY)
+ })
+ })
+ .end()
+ }
+}
diff --git a/packages/mip/test/specs/components/mip-bind.spec.js b/packages/mip/test/specs/components/mip-bind.spec.js
index efa498f4..d418bd71 100644
--- a/packages/mip/test/specs/components/mip-bind.spec.js
+++ b/packages/mip/test/specs/components/mip-bind.spec.js
@@ -168,7 +168,7 @@ describe('mip-bind', function () {
title: 'changed'
})
- MIP.$recompile()
+ // MIP.$recompile()
expect(window.m.global).to.eql({
data: {
@@ -264,6 +264,13 @@ describe('mip-bind', function () {
expect(MIP.getData('loading')).to.be.true
})
+
+ it('should compile smoothly even if data turn to null', function () {
+ MIP.setData({loc: null})
+ MIP.$recompile()
+
+ expect(window.m.loc).to.be.null
+ })
})
describe('watch', function () {
diff --git a/packages/mip/test/specs/vue-custom-element/utils/props.spec.js b/packages/mip/test/specs/vue-custom-element/utils/props.spec.js
index 3c87d9fa..8c7c3ea8 100644
--- a/packages/mip/test/specs/vue-custom-element/utils/props.spec.js
+++ b/packages/mip/test/specs/vue-custom-element/utils/props.spec.js
@@ -73,6 +73,17 @@ describe('vue-custom-element/utils/props', function () {
propB: Number
}
})
+
+ expect(getProps({
+ props: ['prop-a', 'prop-b', 'prop-b']
+ })).to.deep.equal({
+ camelCase: ['propA', 'propB'],
+ hyphenate: ['prop-a', 'prop-b'],
+ types: {
+ propA: String,
+ propB: String
+ }
+ })
})
it('not defined', function () {
@@ -154,6 +165,30 @@ describe('vue-custom-element/utils/props', function () {
})
})
+ it('mixins2', function () {
+ let mixins2 = {
+ props: {
+ attr: Boolean
+ }
+ }
+
+ let mixins1 = {
+ mixins: [mixins2]
+ }
+
+ let props = getProps({
+ mixins: [mixins1]
+ })
+
+ expect(props).to.deep.equal({
+ camelCase: ['attr'],
+ hyphenate: ['attr'],
+ types: {
+ attr: Boolean
+ }
+ })
+ })
+
it('extends', function () {
let props = getProps({
extends: {
@@ -171,12 +206,12 @@ describe('vue-custom-element/utils/props', function () {
})
expect(props).to.deep.equal({
- camelCase: ['propA', 'propB', 'propC'],
- hyphenate: ['prop-a', 'prop-b', 'prop-c'],
+ camelCase: ['propB', 'propC', 'propA'],
+ hyphenate: ['prop-b', 'prop-c', 'prop-a'],
types: {
+ propC: Number,
propA: String,
- propB: Number,
- propC: Number
+ propB: Number
}
})
})