Git Product home page Git Product logo

article's Issues

一起来做Chrome/Firefox Extension《一些问题》

一起来做Chrome/Firefox Extension《一些问题》

更新时间 说明
2019.4.16 增加:关于chrome.tabs.getCurrent()

目录

  1. Unchecked runtime.lastError: The message port closed before a response wa received.
  2. 使用 eval
  3. Content script注入iframe
    1. Extenstion内的html
    2. 站外连接
  4. 关于chrome.tabs.getCurrent()

1. Unchecked runtime.lastError: The message port closed before a response wa received.

此错误一般发生在background js和content js通讯的时候,问题描述得也非常清楚,解决方法非常简单,即在收到消息后,在同步时间里send respnose就可以了。

注意:send response如果在异步方法里,并不能解决这个问题。

// Example:
// background js 或content js
chrome.extension.onMessage.addListener(function(request, _, sendResponse) {
    sendResponse('');
});

2. 使用eval

Chrome Extension默认是禁止使用eval方法的,使用之前,需要先在manifest.json里开启,如下:

"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"

3. 使用iframe

注入iframe同样受content_security_policy限制,而且会受到目标站点的content_security_policy限制。关于content_security_policy内容比较多,这里分成两种情况

3.1 Extension内的html

注入的iframe加载Extension内的html稍微没有这么麻烦,只需要在manifest.json里指定要加载的html就好了

"web_accessible_resources": ["example.html"]

注入iframe的src,可以使用chrome.runtime.getUrl()来获取地址

let src = chrome.runtime.getURL('example.html')

注:要注入网站的content_security_policy对这样的iframe注入会不会有影响,目前还没有测试到。

此方法在 Fika (reader-mode) 扩展里有使用

3.2 站外连接

如上所说,注入iframe是受目标网站的content_security_policy限制的,所以,如果目标网站不允许,你的注入将会失败,如medium.com的content_security_policy关于frame-src的部分:

default-src 'self';

...

frame-src chromenull: https: webviewprogressproxy: medium: 'self';

...

它允许了https的地址,所以,注入的iframe加载https地址是没有问题的,而http的地址将被拒绝。因为注入已经离开了Chrome Extenstion的范围,所以,不管你怎么对Chrome Extension的content_security_policy进行设置并不会有用。

关于content_security_policy,可以看 https://www.html5rocks.com/en/tutorials/security/content-security-policy/

4.关于chrome.tabs.getCurrent()

首先看看官方文档说明

Gets the tab that this script call is being made from. May be undefined if called from a non-tab context (for example, a background page or popup view).

可以看到,它只说无法在没有tab的context里得到,如background page和popup,正常人都会觉得它可以在content script里使用,但其实chrome.tabs API只能在background page和extension单独页面里使用,content script是不能使用chrome.tabs API的,所以getCurrent()是只能在textension单独页面里使用的。

那么如果想要打开了个未激活的tab时做一些操作browser icon的操作时,如果正确的获取脚本运行页面的tab id呢?如:右键链接在新Tab里打开,chrome会新开了一个tab打开页面,但这个tab并未激活(激活指点击tab,这这个tab处于浏览器的活动界面里)。

我在这里做两件事:

  1. 监听tab的激活事件,激活时往content script发消息
  2. content script接到消息检查自己的状态对象,如果OK给background发消息,通知它处理browser icon的变化。
chrome.tabs.onActivated.addListener(function(activeInfo) {
	let tabId = activeInfo.tabId;
	// 给对应tabId的content script发消息
});

问题:

当onActivated解法的时候,如果当前页面没有content script注入接受并返回response,就会报一个接受方法不存在的错误。如打开扩展管理界面。好在并不会有什么影响,扩展发布后,也不会有这个报错。

所以,其实最好是能在content script里使用chrome.tabs.getCurrent()API来处理这类事情。

一起来做Chrome/Firefox Extension《页面右键菜单》

一起来做Chrome Extension《页面右键菜单》


本文主要内容

  • contextMenus的设置
    • 打开权限
    • 创建菜单
    • 点击菜单
  • background script向content script发送消息

预览

WechatIMG2.png

1. contextMenus的设置

1.1 打开权限

contextMenus同其它功能一样,都需要在permissions里指定开启,所以非常简单,在项目的manifest.json文件的permissions中加上contextMenus

"permissions": [
    "contextMenus"
]

这样就可以在background script里使用contextMenus了

1.2 创建右键菜单

首先需要明确,创建菜单以及菜单的事件都是由background script操作的,不是content script,不要被菜单在页面上的现象蒙蔽了。所以,在manifest.json里,一定要配置background scripts(这是重点,勾上,要考)

在background script里,创建菜单代码:

chrome.contextMenus.create({
    type: 'normal',
    title: 'Menu Demo',
    id: 'menuDemo',
    contexts: ['all'],
    onclick: genericOnClick
}, function () {
    console.log('contextMenus are create.');
});

create方法第一个参数是菜单信息对象,具体可以查看:https://developer.chrome.com/extensions/contextMenus

PS: type为separator时,目前版本并不会生效,菜单会直接变成子集菜单的形式,应该是一个bug,在论坛上2010年时有人提过,现在还存在,不知道是为什么

这里主要提的是title,id,onclick

title: 很显示就是menu的名字
id: 当然就是它的ID,点击后要判断点的是谁,就得靠它了,所以名字好好取
onclick: 点击事件,跟的就是处理的方法名,如genericOnClick,就是对应的一个function

1.3 点击菜单

使用create参数的onclick或是监听事件方法都可以对菜单的点击事件进行监听,它们的回调函数都会带两个参数,info和tabs

function genericOnClick(info, tab) {
    // do something.
}

info 是一个字典数据,包含页面及菜单的一些信息,以及在页面上选中的内容文本

{
    editable: false
    frameId: 0
    menuItemId: "menuDemo"
    pageUrl: "https://www.colorgamer.com/"
    selectionText: "colorgamer"
}

信息一目了然

tab 同样是一个字典,包含页面比较具体的一些信息,如tab id等信息,页面宽度等,具体可以自行查看,这里我们将用到tab的id。

那么菜单有了,事件也有了,接下来的问题就是,通过contextMenus拿到的信息,执行的操作都是在background script里的,那如何传回content script里呢?因为很多事情还是要在页面上处理,而不是后台处理。

在前面一篇一起来做chrome扩展《AJAX请求》,我们说过content script如果向background script发送消息,其实倒过来也是成立的,只是有一点(重点,要考)

每个extension的后台都只有一个,而tab有无数个,所以,每个tab向background script发送消息不需要指定什么就能送达,而倒过来后,background script要向哪个tab发送消息呢?

很明显,我们要告诉它,它才会知道,所以这里分两步

  1. 获取当前活动中的tab,因为活动中的就是你看的
  2. 向这个tab发送消息
// 向该tab发送消息
chrome.tabs.sendMessage(tab.id, {'contextMenuId': info.menuItemId, 'info': info}, function(response) {});

发送消息,之前我们使用的是chrome.extension.sendMessage,这里使用chrome.tabs.sendMessage,很明显,是向指定的tab发送,sendMessage方法有三个参数

  • 第一个参数是tab的ID
  • 第二个参数是发送的数据对象
  • 第三个就是回调函数了,有什么要传回来的,都是通过它进行

content script接收消息和之前一样

chrome.extension.onMessage.addListener(function(request, _, response) {
    console.log(request);
});

request即是sendMessage的第二个参数的数据对象,response当然就是回调函数了。

好了,关于Chrome Extension的contextMenus的使用就这些内容,关于contextMenus更多的信息可以参考官方文档。

谢谢您的阅读,有任何问题都可以联系我。

一起来做Chrome/Firefox/Edge Extension《使用eval类方法》

一起来做Chrome/Firefox/Edge Extension《使用eval类方法》

因为FireFox已经使用了Chrome相同的Extension内核,所以为Chrome制作的Extension可以几乎无缝的发布到FireFox里。但Firefox对代码的要求比Chrome要高,比如今天的主角eval类方法就不允许在Extension里使用,Chrome是可以使用的。本文就是介绍一下相关的内容以及如何去支持。

eval类方法

简单说明一下,eval类方法,除了eval还有new Function,这两个方法要在Extension里使用,需要在manifest.json里开启安全策略,如:

"content_security_policy": "'unsafe-eval'",

不然就会报错:

Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' blob: filesystem:".

再看看Firefox code review拒绝说明:

Extensions defining a content security policy that allows eval ('unsafe-eval') are generally not allowed for security and performance reasons. eval is only necessary in rare cases. Please use a different method.

所以,我们是无法在Firefox Extension(Add-ons)里使用eval类方法的。

为什么要使用eval类方法

最明显的原因,如:模板解析。我们知道,没有模板解析在渲染数据时会非常的麻烦和不好看,而大多数的模板解析都会使用eval来将字符串转为js function,以便调用。这其实是JS非常有优势的地方,几个简单的正则就可以有一整套模板解析机制。可惜无法简单的使用它们来帮助开发。

解决方法

使用沙盒模式

在Chrome的文档:Using eval in Chrome Extensions. Safely有讲解,如果使用沙盒模式来安全的使用eval的模板解析库,可以直接参考:https://developer.chrome.com/extensions/sandboxingEval

因为Firefox和Edge都是使用Chrome同种的Extension,所以肯定是通用的。

它的问题:

因为它实际是使用iframe模式,所以当你的模块拆分比较细的时候,并不好处理,而且不是所有Extension都好使用它,所以它肯定不是一个好的选择。

预编译模式

这其实也是Chrome推荐的一种方法,使用的时候先编译好,调用的是编译后的,这样就可以完全的避免使用eval类方法。但这里仍然有些问题,最主要是如何兼容开发/发布模式。这里可以有两种方法:

  1. watch监听文件修改,执行编译。很多库也有使用此方法
  2. 开发保持使用eval不变,发布编译时将其移除,同时编译模板,并更换调用的代码

我不是太喜欢watch监听的方式,因为不管怎么样,都有可能慢一拍,体验不好,所以选择第2种,也是YuiAPI使用的方法,简单说明。

开发的时候,保持使用eval不变

mainfest.json:

"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"

解析:

evalViewFunc: function(model, name) {
    let key = model + "." + name;
    if (!this.hasOwnProperty(key)) {
        let content = this[model][name](),
            _html = [];
        let txt = ["App.view.extend('"+ model +"."+name+"', function() {"];
        txt.push("this.init = function(data) {");
        txt.push("var html = '';");
        content = content.split('\n');
        for (let i in content) {
            _html.push(this.parse(content[i]));
        }
        txt.push(_html.join(""));
        txt.push("return html;");
        txt.push("}");
        txt.push("});");
        eval(txt.join(""));
    }
}

编译的时候

  1. 读取mainfest.json将content_security_policy去掉后,再生成新的mainfest.json文件
  2. 根据模板信息,编译生成一个view.js,将引用模板的调用去掉,最后引用view.js

这样发布后view.js会直接运行,而在开发中使用view的方法都没有问题。

可以参考:https://github.com/yuiitsu/YuiAPI

一起来做Chrome/Firefox Extension《AJAX请求》

一起来做chrome Extension《AJAX请求》

chrome在一次更新之后,出于安全考虑,完全的禁止了content_script从https向http发起ajax请求,即使正常情况下也会在console里给出提示。这对于WEB来讲是好事,但对于扩展来讲就是坏事。平时可以很容易的请求数据,现在就没那么容易了。好在chrome还提供了background_script,利用content_script和background_script之前的通信来实现ajax的请求,就跳过了chrome的这一限制。

content_script

从名字里就知道,content_script是植入型的,它会被植入到符合匹配的网站页面上。在页面加载完成后执行。content_script最有用的地方是操作网站页面上的DOM。一切平时做前端的一些操作它都可以做,像什么添加、修改、删除DOM,获取DOM值,监听事件等等,都可以很容易的做到。所以,如果想获取人家的登录帐户和密码,就是件非常容易的事,只需要添加content_script,监听帐户和密码的文本框,获得值后将数据发送到自己的服务器就可以了。因此,特别说明,别乱装扩展,特别是不从官方扩展库里下载的扩展。

使用content_script

要使用content_script,需要在manifest.json中配置,如下:

{
	"manifest_version": 2,
	"name": "My Extension",
	"description": "Extension description",
	"version": "1.0",

	"content_scripts": {
		"js": [
			"content.js"
		]
	}
}

这样,在页面加载完成后,就会加载content.js,在content.js里,就可以控制页面元素。如果要在content.js中使用jquery,需要将jquery文件加到content.js前面,如:

content_script使用jquery

{
	"content_scripts": {
		"js": [
			"jquery.js",
			"content.js"
		]
	}
}

除了可以加载js,content_scripts里还可以加载CSS文件,这样可以让你的扩展漂亮一点,如:

content_script使用CSS

{
	"content_scripts": {
		"js": [
			"content.js"
		],
		"css": ["style.css"]
	}
}

在content_scripts中,还有一项重要的设置就是matches,它是用来配置,符合扩展使用的网址,如:我只想这个扩展在打开www.jgb.cn时才启用,那么matches就要这样写:

设置匹配网站

{
	"content_scripts": {
		"js": [
			"content.js"
		],
		"css": ["style.css"]
	},
	"matches": [
		"http://*.jgb.cn/*"
	]
}

如果还要匹配www.amazon.com,那就加上:

{
	"matches": [
		"http://*.jgb.cn/*",
		"http://*.amazon.com/*"
	]
}

注意,http只适用于http,像amazon.com这样的站即有http也有https,所以得把https也加上,如下:

{
	"matches": [
		"http://*.jgb.cn/*",
		"http://*.amazon.com/*",
		"https://*.amazon.com/*"
	]
}

background_script

它在chrome扩展启动的时候就启动了,做着它的事,而且等待着你给他的指令。它没办法控制页面元素,但可以通过content_script告诉它。ajax同理,如果要在页面打开时向别的服务器请求数据,这时就可以告诉background_script,让它去请求,然后把返回的数据发送给content_script。这样就不会受到浏览器的安全限制影响。

使用background_script

要使用background_script,需要在manifest.json中配置,如下:

{
	"manifest_version": 2,
	"name": "My Extension",
	"description": "Extension description",
	"version": "1.0",

	"background": {
		"scripts": [
			"background.js"
		]
	}
}

使用jquery和content_scripts同理,需要把jquery文件加到background.js前面,如:

在background_script中使用jquery

{
	"background": {
		"scripts": [
			"jquery.js",
			"background.js"
		]
	}
}

跨域

默认情况下Ajax是不允许跨域的,但扩展提供了跨域的配置,在前一篇《基础介绍》中提到过,那就是permissions,它除了可以让扩展使用chrome的一些功能外,还可以允许JS实现对目录网站的跨域访问,如:

{
	"permissions": [
		"http://www.jgb.cn/" // 允许跨域访问www.jgb.cn
	]
}

有了以上的配置,这时候就可以来看看怎样通过background_scripts来实现Ajax请求了。

向background发送请求

在content_script中向background_script发送请求有好几种方式,这里只列出我常的一种,应该来讲,能满足大多数情况的使用,其它方法,请查询文档,方法如下:

chrome.extension.sendMessage({}, callBack);

sendMessage()方法,它有两个参数,第一个要发送的数据,就像post请求一样,第二个是回调函数。如在content_script中,点击一个按钮,将一个字符串发送到background_script

$(function(){
	$("#button").click(function(){
		chrome.extension.sendMessage({'txt': '这里是发送的内容'}, function(d){
			console.log(d); // 将返回信息打印到控制台里
		});
	});
})

在background中监听content请求

在background中监听content请求,使用chrome.extension.onMessage.addListener(),示例如下:

chrome.extension.onMessage.addListener(function(objRequest, _, sendResponse){});

objRequest,即为请求的参数,在上一个例子就是{'txt': '这里是发送的内容'},可以通过objRequest.txt来获取内容。其实就是一个字典。

sendResponse,为返回值方法,可以将数据返回给content_script,那么一个简单的例子就是:

chrome.extension.onMessage.addListener(function(objRequest, _, sendResponse){
	var strText = objRequest.txt;
	// 将信息能过Ajax发送到服务器
	$.ajax({
		url: 'http://www.jgb.cn/',
		type: 'POST',
		data: {'txt': strText},
		dataType: 'json',
	}).then(function(){
		// 将正确信息返回content_script
		sendResponse({'status': 200});
	}, function(){
		// 将错误信息返回content_script
		sendResponse({'status': 500});
	});
});

这样一去一来,也就实现content_script向background_script发送请求,并使用background_script执行ajax请求的目的,非常的简单好用

在此基础上,增加一些条件和数据,就可以很好的实现接收,发送数据的操作。比如向自己的服务器请求或发送数据。

通过修改chrome启动参数,实现可在https页面向http页面发起ajax请求

除了使用background_script来发起Ajax请求外,还可以通过修改chrome的启动参数来达到这个目的。参数为:--allow-running-insecure-content,操作方法:

  1. 右键chrome快捷方式,选择属性
  2. 在目标的最后,输入--allow-running-insecure-content,中间有个空格

这样chrome就可以允许你在https页面向http发起ajax请求了。这个方法可以达到目的,但不推荐,因为不科学。

一起来做Chrome/Firefox Extension《本地存储localStorage》

一起来做chrome Extension《本地存储localStorage》

chrome中的本地存储其实也是用的HTML5中localStorage,唯一区别是chrome扩展有自己的localStorage,它属于这个扩展,而不属于一个域名。得用这一点可以很好的处理扩展自己的一些数据,而不受访问网站和域名的影响。

localStorage基础

localStorage是HTML5特性,所以有些浏览器不一定支持,不过我们这里讲的是chrome扩展,所以完全不用担心这个问题。如果想在WEB页面上使用,那就要检查一下是不是支持它

可以这样的检测:

if(window.localStorage){
	console.log('支持');
}else{
	console.log('不支持');
}

localStorage和memcache一样,是key/value的存储类型,所以,除非你只存字符串,不然就得以json的形式来存储。之后解析成数组,就可以很好的使用里面的值。

增删改查:

// 存储/修改
localStorage.name = 'only';
localStorage['name'] = 'only';

// 删除
localStorage['name'] = null; 删除一个
localStorage.clear(); 删除所有

// 查
var name = localStorage.name;
var name = localStorage['name'];

localStorage是不能跨域的,所以不同域名的localStorage是互不干扰的。比如在jgb.com上存储了一个值,在www.jgb.com是访问不了它的,反之亦然。

chrome扩展中的localStorage

扩展中的localStorage没什么不同,只是有一个注意点,content_script中的localStorage是存储在对应域名下的,所以别的域名是不能访问的。background_script中的localStorage是存储在chrome扩展下的,所以不管什么域名都可以访问它。这一点很重要,如果没有这个特性,扩展的应用场景就会少很多很多。

查看localStorage

查看对应域名的localStorage

右键选择审查元素,如图选择

image

查看扩展的localStorage

打开扩展界面,打开你扩展的背景页

image

如图选择

image

简单示例

这个示例很简单,扩展将匹配www.jgb.cn和www.amazon.com,打开两个网站后,会在页面中间显示一个列表和一个表单,列表是以前填入的名字,表单可以填入你名字另外的名字。列表里,可以删除一条,也可以删除所有名字。简单也讲,就是一个增删查。在这个例子中,可以看到,在www.jgb.cn和www.amazon.com中都是可以读取和操作这些存储内容的,不受域名的限制。

第一步:建立文件夹

目录名就叫localstorage,目录中的结构基本如图:

image

里面除了mainfest.json是必须的,其它东西都可以按自己的习惯来

第二步:建立mainfest.json文件

之前的日志已经讲过这个文件,所以这里就直接帖内容

{
	"manifest_version": 2,
	"name": "一起来做chrome扩展之本地存储",
	"version": "0.1",
	"description": "一个简单的本地存储例子",
	"background": {
		"scripts": [
			"include/jquery-1.11.0.min.js",	
			"scripts/background.js"
		]
	},
	"content_scripts": [{
		"matches": [
			"http://*.jgb.cn/*",
			"http://*.amazon.com/*"
		],
		"css": ["css/common.css"],
		"js": [
			"include/jquery-1.11.0.min.js",
			"scripts/main.js"
		]
	}]
}

如果对这个文件不熟悉,可以看看一起来做chrome扩展《基础介绍》

把jquery拷贝到include中,再到scripts目录建立main.js和background.js

第三步:创建界面

这里我们创建一个简单的界面,在目标网站的正中间,显示一个500*300的浮动层,正常js,写法随意

var main = {
	
	/**
	 * 创建界面
	 */
	createHtml: function(){
		var _html = '<div id="ls_box">'+
			'<h3>'+
				'本地存储 local storage'+
			'</h3>'+
			'<div id="ls_list">'+
				'正在加载数据...'+
			'</div>'+
			'<div id="ls_form">'+
				'<label>'+
					'新增: '+
				'</label>'+
				'<input type="text" id="ls_message" />'+
				'<button id="ls_save">保存</button>'+
			'</div>'+
		'</div>';
		$('body').append(_html);
	}
}

界面大致如下

image

第四步:向background发送消息

方法很简单

/**
 * 向background发送消息
 * @params strAction string 执行方法
 * @params dicData dict 数据字典
 * @params callback function 回调函数
 */
sendMessageBack: function(strAction, dicData, callback){
	chrome.extension.sendMessage({'action': strAction, 'data': dicData}, callback);
},

第五步:将存储内容输出到页面

/**
 * 将已有数据写到页面上
 */
showList: function(dicList){
	if(!dicList || dicList.length == 0){
		$("#ls_list").html('<p>没有找到数据</p>');
		return;
	}

	// 遍历对象,构建输出html
	var _html = ['<ul>'];
	for(var i in dicList){
		_html.push('<li><span class="ls_del" data-item="'+dicList[i]+'">X</span>'+dicList[i]+'</li>');
	}
	_html.push('</ul>');
	$("#ls_list").html(_html.join(''));

	// 监听删除
	_this.listenDel();
},

第六步:监听保存

一个简单的单击事件,把文本内容发送给background.js,然后将返回的数据利用上面的方法输出到页面上

/**
 * 监听保存事件
 */
listenSave: function(){
	_this = this;

	$("#ls_save").click(function(){
		// 获取message
		var strMessage = $.trim($('#ls_message').val());
		if(!strMessage){
			return false;
		}
		// 通知background,保存数据
		_this.sendMessageBack('save', {'message': strMessage}, function(response){
			if(response.status == 200){
				// 将内容输出到页面
				_this.showList(response.data);
				$('#ls_message').val('');
			}
		});
	});
},

第七步:监听删除

删除和保存同理,只是发送到background的请求方法不同,正常JS操作,这里就不帖代码,最后看看background.js的监听与返回消息

第八步:background监听消息与返回

/**
 * 监听content_script发送的消息
 */
chrome.extension.onMessage.addListener(function(request, _, sendResponse){
	// 返回数据
	var dicReturn;

	// 读取已存数据
	if(request.action == 'list'){
		// 从localstorage中读取数据
		var strList = localStorage['list'];
		if(strList){
			// 将json字符串转为对象
			var dicList = JSON.parse(strList)
			dicReturn = {'status': 200, 'data': dicList}
		}else{
			dicReturn = {'status': 404}
		}

		// 向content_script返回信息
		sendResponse(dicReturn);
	}

	// 保存
	if(request.action == 'save'){
		// content_script传来message
		var strMessage = request.data.message;
		// 从localstorage中读取数据
		var strList = localStorage['list'];
		var dicList = [];
		if(strList){
			// 将json字符串转为对象
			dicList = JSON.parse(strList)
		}
		dicList.push(strMessage);
		localStorage['list'] = JSON.stringify(dicList);

		dicReturn = {'status': 200, 'data': dicList};
		// 向content_script返回信息
		sendResponse(dicReturn);
	}

	// 删除
	if(request.action == 'del'){
		// content_script传来的message
		var strMessage = request.data.message;
		// 从localstorage中读取数据
		var strList = localStorage['list'];
		if(strList){
			// 将json字符串转为对象
			dicList = JSON.parse(strList);
			// 遍历数据,找到对应值
			for(var i in dicList){
				if(dicList[i] == strMessage){
					// 删除该值
					dicList.splice(i, 1);
				}
			}

			// 重新存储
			localStorage['list'] = JSON.stringify(dicList);
			// 向content_script返回信息
			sendResponse({'status': 200});
		}else{
			sendResponse({'status': 501, 'msg': '删除失败,未有数据'});
		}
	}
})

接收content_script发来的数据,使用request.data.message,data.message都是自己定义的key,所以想传什么,都可以自己定

返回值使用sendResponse()方法,内容为一个对象,content_script接受到后,可以直接使用,所以就有了if(response.status == 200){}这样的写法。

一个简单的本地存储就是这样了,加载到chrome或是同一内核的浏览器中,打开www.jgb.cn或是www.amazon.com,就可以看到扩展界面,保存一些数据,在两个网站刷新,会发现,数据都是可以获取的。

源代码

本示例代码:https://github.com/onlyfu/localstorage

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.