Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mip iframe 增加 https 检查、ascii 转码、src 和 srcdoc 属性监听 #329

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 55 additions & 30 deletions docs/extensions/builtin/mip-iframe.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
标题|内容
----|----
类型|通用
支持布局|responsive, fixed-height, fixed
支持布局|responsive, fixed-height, fixed, fill, container
所需脚本|无

## 示例
Expand Down Expand Up @@ -59,63 +59,88 @@
</mip-iframe>
```

### 加 `encode` 转码中文字符

```html
<mip-iframe
allowfullscreen
srcdoc="
<div>你好</div>
"
encode
width="400"
height="300"
sandbox=""
allowtransparency="true">
</mip-iframe>
```

## 属性

### src

说明:与原生 `<iframe>` 的 `src` 属性作用一致
必选项:是
类型:URL
单位:无
取值:必须要使用 HTTPS 地址
说明:与原生 `<iframe>` 的 `src` 属性作用一致
必选项:是
类型:URL
单位:无
取值:必须要使用 HTTPS 地址
默认值:无

### width

说明:宽度,不是实际宽度,与高度属性配合来设置图片比例,详见[组件布局](../../guide/component/layout.md)
必选项:是
类型:数字
单位:无
必选项:是
类型:数字
单位:无
默认值:无

### height

说明:高度,不是实际高度,与宽度属性配合来设置图片比例,详见[组件布局](../../guide/component/layout.md)
必选项:是
类型:数字
单位:无
必选项:是
类型:数字
单位:无
默认值:无

### allowfullscreen

说明:与原生 `<iframe>` 的 `allowfullscreen` 属性作用一致
必选项:否
取值:空
说明:与原生 `<iframe>` 的 `allowfullscreen` 属性作用一致
必选项:否
取值:空
默认值:无

### srcdoc

说明:与原生 `<iframe>` 的 `srcdoc` 属性作用一致
必选项:否
类型:HTML_code
单位:无
取值:要显示在 `<iframe>` 中的 HTML 内容。必需是有效的 HTML 语法
说明:与原生 `<iframe>` 的 `srcdoc` 属性作用一致
必选项:否
类型:HTML_code
单位:无
取值:要显示在 `<iframe>` 中的 HTML 内容。必需是有效的 HTML 语法
默认值:无

### encode

说明:是否对 `srcdoc` 的内容做 unicode 转 ascii,如果 srcdoc 存在中文等未做 ascii 转码的字符,需要设置该属性
必选项:否
类型:字符串
单位:无
取值:"", true, false
默认值:false

### sandbox

说明:与原生 `<iframe>` 的 `sandbox` 属性作用一致
必选项:否
类型:字符串
单位:无
取值:"", `allow-same-origin`, `allow-top-navigation`, `allow-forms`, `allow-script`
说明:与原生 `<iframe>` 的 `sandbox` 属性作用一致
必选项:否
类型:字符串
单位:无
取值:"", `allow-same-origin`, `allow-top-navigation`, `allow-forms`, `allow-script`
默认值:无

### allowtransparency

说明:与原生 `<iframe>` 的 `allowtransparency` 属性作用一致
必选项:否
类型:字符串
单位:无
取值:"", true, false
说明:与原生 `<iframe>` 的 `allowtransparency` 属性作用一致
必选项:否
类型:字符串
单位:无
取值:"", true, false
默认值:无
26 changes: 26 additions & 0 deletions packages/mip/examples/builtin-components/mip-iframe.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,32 @@ <h2>加 srcdoc</h2>
allowtransparency="true">
</mip-iframe>

<h2>加带中文的 srcdoc</h2>

<pre class="code">&lt;mip-iframe
allowfullscreen
srcdoc="
&lt;div&gt;你好&lt;/div&gt;
"
encode="true"
width="400"
height="300"
sandbox=""
allowtransparency="true"&gt;
&lt;/mip-iframe&gt;</pre>

<mip-iframe
allowfullscreen
srcdoc="
<div>你好</div>
"
encode
width="400"
height="300"
sandbox=""
allowtransparency="true">
</mip-iframe>



</body>
Expand Down
121 changes: 99 additions & 22 deletions packages/mip/src/components/mip-iframe.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/**
* @file mip-iframe
* @author zhangzhiqiang([email protected])
* clark-t ([email protected])
*/

import util from '../util/index'
import fn from '../util/fn'
import CustomElement from '../custom-element'
import viewport from '../viewport'
import {
Expand All @@ -12,43 +14,107 @@ import {
MESSAGE_PAGE_RESIZE
} from '../page/const'

let attrList = ['allowfullscreen', 'allowtransparency', 'sandbox']
const ATTR_LIST = [
'allowfullscreen',
'allowtransparency',
'sandbox',
'referrerpolicy'
]

function encode (str) {
let arr
if (typeof window.TextEncoder !== 'undefined') {
arr = new window.TextEncoder('utf-8').encode(str)
} else {
arr = new Uint8Array(str.length)
str = unescape(encodeURIComponent(str))
for (let i = 0; i < str.length; i++) {
arr[i] = str.charCodeAt(i)
}
}
// 不能直接 arr.map().join()
let output = new Array(arr.length)
for (let i = 0; i < arr.length; i++) {
output[i] = String.fromCharCode(arr[i])
}
return output.join('')
}

class MipIframe extends CustomElement {
build () {
this.handlePageResize = this.handlePageResize.bind(this)
this.notifyRootPage = this.notifyRootPage.bind(this)
let element = this.element
let src = element.getAttribute('src')
let srcdoc = element.getAttribute('srcdoc')
if (srcdoc) {
src = 'data:text/html;charset=utf-8;base64,' + window.btoa(srcdoc)
constructor (...args) {
super(...args)

this._src = null
this.updateIframeSrc = fn.throttle(this.setIframeSrc.bind(this))
}

static get observedAttributes () {
return ['src', 'srcdoc']
}

attributeChangedCallback (name, oldValue, newValue) {
if (this.isBuilt) {
this[name] = newValue
this.updateIframeSrc()
}
}

let height = element.getAttribute('height')
let width = element.getAttribute('width') || '100%'
set src (value) {
// 当 srcdoc 存在时,优先展示 srcdoc 的内容
if (this.element.getAttribute('srcdoc')) {
return
}
// url 必须是 https
if (value == null || value.slice(0, 8) === 'https://') {
this._src = value
} else {
this.srcdoc = `Invalid &lt;mip-iframe&gt; src. Must start with https://`
}
}

if (!src || !height) {
set srcdoc (value) {
// 当删除 srcdoc 属性时,显示 src 的内容
if (value == null) {
this.src = this.element.getAttribute('src')
} else if (this._srcdoc !== value) {
this._srcdoc = value
// 兼容 mip1
let needEncode = this.element.getAttribute('encode')
if (needEncode === 'true' || needEncode === '') {
value = encode(value)
}
this._src = 'data:text/html;charset=utf-8;base64,' + window.btoa(value)
}
}

setIframeSrc () {
if (!this._src) {
return
}

// window.addEventListener('message', )
window.addEventListener('message', this.notifyRootPage.bind(this))
if (this.iframe) {
if (this.iframe.src !== this._src) {
this.iframe.src = this._src
}
return
}

let height = this.element.getAttribute('height')
let width = this.element.getAttribute('width') || '100%'

let iframe = document.createElement('iframe')
iframe.frameBorder = '0'
iframe.scrolling = util.platform.isIos() ? /* istanbul ignore next */ 'no' : 'yes'

util.css(iframe, {
width,
height
})

this.applyFillContent(iframe)
iframe.src = src

this.expendAttr(attrList, iframe)
element.appendChild(iframe)

this.expendAttr(ATTR_LIST, iframe)
iframe.src = this._src
this.element.appendChild(iframe)
this.iframe = iframe

/**
Expand All @@ -70,13 +136,24 @@ class MipIframe extends CustomElement {
}
}

build () {
this.bindedHandlePageResize = this.handlePageResize.bind(this)
this.bindedNotifyRootPage = this.notifyRootPage.bind(this)

window.addEventListener('message', this.bindedNotifyRootPage)

this.srcdoc = this.element.getAttribute('srcdoc')
this.setIframeSrc()
this.isBuilt = true
}

firstInviewCallback () {
window.addEventListener(CUSTOM_EVENT_RESIZE_PAGE, this.handlePageResize.bind(this))
window.addEventListener(CUSTOM_EVENT_RESIZE_PAGE, this.bindedHandlePageResize)
}

disconnectedCallback () {
window.removeEventListener(CUSTOM_EVENT_RESIZE_PAGE, this.handlePageResize.bind(this))
window.removeEventListener('message', this.notifyRootPage.bind(this))
window.removeEventListener(CUSTOM_EVENT_RESIZE_PAGE, this.bindedHandlePageResize)
window.removeEventListener('message', this.bindedNotifyRootPage)
}

notifyRootPage ({data}) {
Expand Down
Loading